132 lines
15 KiB
Python
132 lines
15 KiB
Python
"""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=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',B/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
|
|
L=Path(os.environ.get('ERR_GROUND_DEPOT_LOADING_SOURCE',B/'addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c'))
|
|
assert 'int DepotItemCount(' in S.read_text() and 'bool AdmitDepotMine(' in I.read_text(), 'finite authored camp mine provenance/import API is absent'
|
|
# Read the actual authored configuration, not a duplicated stock fixture.
|
|
conf=(B/'addons/core/Configs/ERR/EveronSites.conf').read_text()
|
|
pmn='{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et'
|
|
camps=[]
|
|
for match in re.finditer(r' ERR_SiteDefinition \{',conf):
|
|
end=match.end();depth=1
|
|
while depth:
|
|
if conf[end]=='{':depth+=1
|
|
elif conf[end]=='}':depth-=1
|
|
end+=1
|
|
block=conf[match.start():end]
|
|
eligible=bool(re.search(r'^ Kind 3$',block,re.M)) and not re.search(r'^ RevealOnEscape 1$',block,re.M)
|
|
stocks=re.findall(r'ERR_SiteStock \{ Prefab "([^"\n]+)" Count (\d+)([^}]*?) \}',block)
|
|
mines=[row for row in stocks if row[0]==pmn]
|
|
if eligible:
|
|
assert len(mines)==1 and mines[0][1]=='2' and 'TargetId' not in mines[0][2]
|
|
camps.append(re.search(r'Id "([^"]+)"',block).group(1))
|
|
else:assert not mines
|
|
assert len(camps)==5
|
|
assert 'ERR_InfantryMineTransfer' not in S.read_text(), 'Core must not depend on the AI mine transfer layer'
|
|
capture=body(S, 'if (spec.Kind == ERR_LocationKind.OUTPOST && !spec.RevealOnEscape && stock.TargetId.IsEmpty()')
|
|
access='\n'.join(body(S,k) for k in ('int DepotMagazineCount(', 'int DepotMineCount(', 'int DepotItemCount(', 'IEntity DepotItem(', 'void DepotStock(', 'IEntity DepotMagazine(', 'bool DepotOriginal('))
|
|
admit='\n'.join(body(I,k) for k in ('bool DepotCurrent(', 'bool DepotAvailable(', 'bool DepotNeeded(', 'bool AdmitDepot(', 'bool DepotMineAvailable(', 'bool DepotMineNeeded(', 'bool AdmitDepotMine(', 'protected bool CaptureMines('))
|
|
delivered=body(L,'bool DeliveredTo(')
|
|
def adapt(s):
|
|
s=s.replace('array<IEntity>','Array<IEntity>').replace('out Array<IEntity> items','Array<IEntity>& items').replace('ERR_SiteRuntime site','Site* site').replace('ERR_GroundDepotLoading job','Loading* job').replace('ERR_InfantryResupply service','Service* service')
|
|
s=s.replace('protected ', '').replace('ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Convoy);','Supply* supply = m_Convoy;')
|
|
s=s.replace('InventoryItemComponent carried = InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent));','InventoryItem* carried = item->inventoryItem;')
|
|
s=s.replace('foreach (IEntity item : m_MineStock)', 'for (IEntity item : m_MineStock)')
|
|
s=s.replace('ERR_InfantryMineTransfer.', 'ERR_InfantryMineTransfer::').replace('ERR_InfantryAmmoTransfer.', 'ERR_InfantryAmmoTransfer::')
|
|
for name in ('supply','m_MineBatch','carried'):s=s.replace(name+'.',name+'->')
|
|
s=s.replace('GetParentSlot().','GetParentSlot()->')
|
|
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 <cassert>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <string>
|
|
using string=std::string;bool server=true;
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}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;using IEntity=Entity*;
|
|
struct Slot{IEntity attached=nullptr;IEntity GetAttachedEntity(){return attached;}};
|
|
struct InventoryItem{Slot* slot=nullptr;Slot* GetParentSlot(){return slot;}};
|
|
struct Entity{bool magazine=true,ready=false,stored=true;Slot slot;InventoryItem inventory;InventoryItem*inventoryItem=&inventory;Entity(){slot.attached=this;inventory.slot=&slot;}};
|
|
// Native ready predicate and storage graph are doubled; actual readiness is separately tested by mine-transfer suites.
|
|
struct ERR_InfantryMineTransfer{static bool ReadyMine(IEntity item){return item&&item->ready;}};
|
|
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity item,IEntity){return item&&item->stored;}};
|
|
struct Def{int Kind=3;bool RevealOnEscape=false;};struct Stock{string TargetId,Prefab;};
|
|
struct Responders{bool Group=true;};struct Site{Def* Definition=nullptr;IEntity Container=nullptr;struct Responders* Responders=nullptr;Array<IEntity>DepotMagazines,DepotMines;};
|
|
struct Sites{bool m_Stopped=false;int m_State=0;Array<Site*>m_Sites;Array<IEntity>m_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,mine=false;bool MineCargo(){return mine;}bool Confirmed(){return done;}bool Delivery(){return delivery;}};
|
|
struct Loading{bool m_Imported=false,valid=true,m_MineCargo=false;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,bool=false);};
|
|
struct Mounted{bool busy=false;bool Busy(){return busy;}};
|
|
struct Truck{IEntity Entity=nullptr;};
|
|
struct Batch{bool complete=true;Array<IEntity>items;bool Complete(){return complete;}int Count(){return items.Count();}IEntity At(int index){return items[index];}};
|
|
struct Supply{Batch*MineStock=nullptr;};
|
|
struct Service{Supply*m_Convoy=nullptr;Batch*m_MineBatch=nullptr;bool m_MineStockCaptured=false;Array<IEntity>m_MineStock;int m_DepotMineImports=0;bool current=true,truckValid=true,unresolved=false,m_Move=false,m_MineMove=false;int m_State=1,audits=0,m_DepotImports=0;Array<IEntity>m_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(').replace('bool mineCargo = false','bool mineCargo')+r'''
|
|
struct F{Entity cache,item,foreign,truck;Def def;Stock stock;Responders responders;Site site;Sites sites;Array<IEntity>original,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};}
|
|
};
|
|
struct M:F{Entity initial;Batch batch;Supply supply;
|
|
M(){item.magazine=false;item.ready=true;initial.magazine=false;initial.ready=true;batch.items={&initial};supply.MineStock=&batch;service.m_Convoy=&supply;service.m_MineBatch=&batch;service.m_Spent={&initial};job.m_MineCargo=true;proof.mine=true;}
|
|
};
|
|
int main(){
|
|
// Authored inert mines join detached startup provenance, without replacing magazine APIs.
|
|
{F f;f.sites.m_State=1;f.stock.Prefab="{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et";Entity mine;mine.magazine=false;mine.ready=true;f.sites.Capture(&f.def,&f.stock,&f.site,&mine);assert(f.sites.DepotMagazineCount(&f.site)==1&&f.sites.DepotMineCount(&f.site)==1&&f.sites.DepotItemCount(&f.site)==2);assert(f.sites.DepotItem(&f.site,1)==&mine&&f.sites.DepotOriginal(&f.site,&mine));Array<IEntity>out;f.sites.DepotStock(&f.site,out);out.clear();assert(f.sites.DepotItemCount(&f.site)==2);Entity other;other.magazine=false;other.ready=true;assert(!f.sites.DepotOriginal(&f.site,&other));f.sites.Capture(&f.def,&f.stock,&f.site,&other);Entity third;third.magazine=false;third.ready=true;f.sites.Capture(&f.def,&f.stock,&f.site,&third);assert(f.sites.DepotMineCount(&f.site)==2&&!f.sites.DepotOriginal(&f.site,&third));}
|
|
for(int bad=0;bad<4;bad++){F f;f.item.magazine=false;f.item.ready=true;f.stock.Prefab="{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et";f.site.DepotMagazines.clear();if(bad==0)f.def.RevealOnEscape=true;if(bad==1)f.def.Kind=4;if(bad==2)f.stock.TargetId="radio";if(bad==3)f.stock.Prefab="foreign";f.sites.Capture(&f.def,&f.stock,&f.site,&f.item);assert(f.site.DepotMines.empty());}
|
|
{M f;assert(f.service.DepotMineNeeded()&&!f.service.DepotNeeded());assert(f.service.AdmitDepotMine(&f.job,&f.item));assert(f.service.m_MineStock.Count()==2&&f.service.m_DepotMineImports==1&&f.service.m_Stock.empty());assert(!f.service.DepotMineNeeded()&&!f.service.AdmitDepotMine(&f.job,&f.item));assert(f.spent.Count()==1&&f.service.m_Spent.Count()==1);ERR_FiniteMagazineReservation next(&f.service.m_MineStock,&f.service.m_Spent,&f.item);assert(next.Reserve()&&next.Commit());assert(f.service.DepotMineNeeded());}
|
|
// Known mine expenditure only; missing/deleted/magazine identities never become mine demand.
|
|
{M f;f.service.m_Spent.clear();assert(!f.service.DepotMineNeeded());f.service.m_Spent={nullptr,&f.foreign};f.service.m_Stock={&f.foreign};assert(f.service.DepotNeeded()&&!f.service.DepotMineNeeded());f.service.m_MineStock={nullptr};assert(!f.service.DepotMineNeeded());}
|
|
for(int bad=0;bad<21;bad++){M f;assert(f.service.DepotMineNeeded());if(bad==0)f.service.current=false;if(bad==1)f.service.m_State=7;if(bad==2)f.batch.complete=false;if(bad==3)f.supply.MineStock=nullptr;if(bad==4)f.service.m_MineBatch=nullptr;if(bad==5)f.service.m_MineStock.resize(96,&f.initial);if(bad==6)f.service.m_Spent.resize(96,&f.initial);if(bad==7)f.item.ready=false;if(bad==8)f.service.m_Stock.Insert(&f.item);if(bad==9)f.service.m_Spent.Insert(&f.item);if(bad==10)f.service.cargo.clear();if(bad==11)f.item.stored=false;if(bad==12)f.item.inventoryItem=nullptr;if(bad==13)f.item.inventory.slot=nullptr;if(bad==14)f.item.slot.attached=&f.foreign;if(bad==15)f.job.m_MineCargo=false;if(bad==16)f.proof.mine=false;if(bad==17)f.job.m_State=7;if(bad==18)f.job.valid=false;if(bad==19)f.proof.done=false;if(bad==20)f.service.m_Spent.clear();int before=f.service.m_MineStock.Count();assert(!f.service.AdmitDepotMine(&f.job,&f.item));assert(f.service.m_MineStock.Count()==before&&f.service.m_DepotMineImports==0);}
|
|
{M f;f.batch.items.clear();f.service.m_Spent.clear();assert(!f.service.DepotMineNeeded()&&!f.service.AdmitDepotMine(&f.job,&f.item));}
|
|
{M f;assert(f.service.DepotMineNeeded());f.service.m_MineStock.resize(95,&f.initial);assert(f.service.AdmitDepotMine(&f.job,&f.item));assert(f.service.m_MineStock.Count()==96&&f.service.DepotCurrent()&&!f.service.DepotMineAvailable());}
|
|
|
|
{F f;Array<IEntity>out;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 mine/magazine provenance and separated finite mine imports; actual authored identity capture, READY-only detached provenance, same shared stock admission, completed exact two-leg proof, lifetime caps and Stop no late adoption')
|