Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,5 @@
Traceback (most recent call last):
File "/tmp/erri-depot-mines-stock-1116/tests/test_ground_depot_provenance.py", line 10, in <module>
assert 'int DepotItemCount(' in S.read_text() and 'bool AdmitDepotMine(' in I.read_text(), 'finite authored camp mine provenance/import API is absent'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: finite authored camp mine provenance/import API is absent
@@ -0,0 +1,282 @@
--- a/addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c
+++ b/addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c
@@ -13,6 +13,7 @@
ERR_LocationComponent Location;
IEntity Container;
ref array<IEntity> DepotMagazines = {}; // Exact authored startup identities; never refreshed from live inventory.
+ ref array<IEntity> DepotMines = {}; // At most two authored inert PMN4 per camp, never replenished.
SCR_AIGroup Group;
ref ERR_SiteRuntime Responders;
string ExpectedGuardFaction;
@@ -58,7 +59,7 @@
if (index < 0 || index >= m_Sites.Count()) return null;
return m_Sites[index];
}
- // Optional ground loading consumes only retained original camp magazines.
+ // Optional ground loading consumes only retained original camp items.
int DepotMagazineCount(ERR_SiteRuntime site)
{
if (!Replication.IsServer() || m_Stopped || m_State != ERR_PrepareResult.READY || !site || !m_Sites.Contains(site)) return 0;
@@ -67,11 +68,33 @@
if (site.DepotMagazines.Count() > 96) return 0;
return site.DepotMagazines.Count();
}
+ int DepotMineCount(ERR_SiteRuntime site)
+ {
+ if (!Replication.IsServer() || m_Stopped || m_State != ERR_PrepareResult.READY || !site || !m_Sites.Contains(site)) return 0;
+ if (!site.Definition || site.Definition.Kind != ERR_LocationKind.OUTPOST || site.Definition.RevealOnEscape) return 0;
+ if (!site.Container || !Owns(site.Container) || !site.Responders || !site.Responders.Group) return 0;
+ if (site.DepotMines.Count() > 2) return 0;
+ return site.DepotMines.Count();
+ }
+ int DepotItemCount(ERR_SiteRuntime site)
+ {
+ if (!site || site.DepotMagazines.Count() > 96 || site.DepotMines.Count() > 2) return 0;
+ int count = DepotMagazineCount(site) + DepotMineCount(site);
+ if (count > 96) return 0;
+ return count;
+ }
+ IEntity DepotItem(ERR_SiteRuntime site, int index)
+ {
+ if (index < 0 || index >= DepotItemCount(site)) return null;
+ int magazines = DepotMagazineCount(site);
+ if (index < magazines) return site.DepotMagazines[index];
+ return site.DepotMines[index - magazines];
+ }
void DepotStock(ERR_SiteRuntime site, out array<IEntity> items)
{
items = {};
- int count = DepotMagazineCount(site);
- for (int i = 0; i < count; i++) items.Insert(site.DepotMagazines[i]);
+ int count = DepotItemCount(site);
+ for (int i = 0; i < count; i++) items.Insert(DepotItem(site, i));
}
IEntity DepotMagazine(ERR_SiteRuntime site, int index)
{
@@ -80,7 +103,7 @@
}
bool DepotOriginal(ERR_SiteRuntime site, IEntity item)
{
- return item && DepotMagazineCount(site) > 0 && site.DepotMagazines.Contains(item);
+ return item && DepotItemCount(site) > 0 && (site.DepotMagazines.Contains(item) || site.DepotMines.Contains(item));
}
bool Owns(IEntity entity) { return entity && m_Entities.Contains(entity); }
// Snapshot only nearby mission-owned objects. Distant sites must not block
@@ -416,7 +439,12 @@
}
ERR_StockPlacement placement = new ERR_StockPlacement();
if (spec.Kind == ERR_LocationKind.OUTPOST && !spec.RevealOnEscape && stock.TargetId.IsEmpty()
- && item.FindComponent(BaseMagazineComponent) && site.DepotMagazines.Count() < 96) site.DepotMagazines.Insert(item);
+ && site.DepotMagazines.Count() + site.DepotMines.Count() < 96)
+ {
+ if (item.FindComponent(BaseMagazineComponent)) site.DepotMagazines.Insert(item);
+ else if (stock.Prefab == "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et"
+ && site.DepotMines.Count() < 2) site.DepotMines.Insert(item);
+ }
placement.Item = item;
placement.Inventory = inventory;
m_Placements.Insert(placement);
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c
@@ -123,6 +123,7 @@
// Admission uses the existing array object so an already-created mounted
// coordinator observes this exact finite addition without a replacement ledger.
protected int m_DepotImports;
+ protected int m_DepotMineImports;
bool DepotCurrent()
{
return Current() && m_State == 1 && !m_Move && !m_MineMove && !Unresolved()
@@ -136,6 +137,32 @@
foreach (IEntity item : m_Spent)
if (item && m_Stock.Contains(item)) committed++;
return committed > m_DepotImports;
+ }
+ // Mine imports replace only proved outgoing original mine commitments.
+ // Keep the startup batch binding and both lifetime identity/spent ceilings.
+ bool DepotMineAvailable()
+ {
+ return DepotCurrent() && CaptureMines() && m_MineStock.Count() < 96 && m_Spent.Count() < 96;
+ }
+ bool DepotMineNeeded()
+ {
+ if (!DepotMineAvailable()) return false;
+ int committed;
+ foreach (IEntity item : m_Spent)
+ if (item && m_MineStock.Contains(item)) committed++;
+ return committed > m_DepotMineImports;
+ }
+ bool AdmitDepotMine(ERR_GroundDepotLoading job, IEntity item)
+ {
+ if (!job || !DepotMineNeeded() || !ERR_InfantryMineTransfer.ReadyMine(item)
+ || m_MineStock.Contains(item) || m_Stock.Contains(item) || m_Spent.Contains(item)) return false;
+ if (!job.DeliveredTo(this, item, true) || !Contains(m_Truck.Entity, item)
+ || !ERR_InfantryAmmoTransfer.StoredUnder(item, m_Truck.Entity)) return false;
+ InventoryItemComponent carried = InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent));
+ if (!carried || !carried.GetParentSlot() || carried.GetParentSlot().GetAttachedEntity() != item) return false;
+ m_MineStock.Insert(item); m_DepotMineImports++;
+ Audit("one exact finite camp PMN4 admitted after proved physical courier delivery");
+ return true;
}
ERR_ConvoyVehicle DepotTruck()
{
@@ -554,7 +581,7 @@
string Status()
{
string detail = string.Format("Ground supply: state=%1 trips=%2 infantry moved=%3 stock=%4 spent=%5", State(), m_Trip, m_Delivered, m_Stock.Count(), m_Spent.Count());
- return detail + string.Format("; mines moved=%1 initial=%2; %3", m_MinesDelivered, m_MineStock.Count(), m_Reason);
+ return detail + string.Format("; mines moved=%1 allowance=%2; %3", m_MinesDelivered, m_MineStock.Count(), m_Reason);
}
void Stop()
{
--- a/addons/core/Configs/ERR/EveronSites.conf
+++ b/addons/core/Configs/ERR/EveronSites.conf
@@ -131,6 +131,7 @@
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
}
Stock {
+ ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
@@ -254,6 +255,7 @@
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
}
Stock {
+ ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
@@ -283,6 +285,7 @@
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
}
Stock {
+ ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
@@ -312,6 +315,7 @@
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
}
Stock {
+ ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
@@ -341,6 +345,7 @@
ERR_SiteProp { Prefab "{CFFEB23FBEA00322}Prefabs/Props/Military/Sandbags/Sandbag_01_short_burlap.et" Offset 5 0 -7 }
}
Stock {
+ ERR_SiteStock { Prefab "{B05A816C0BF50802}Prefabs/Weapons/Explosives/Mine_PMN4/Mine_PMN4.et" Count 2 }
ERR_SiteStock { Prefab "{FA5C25BF66A53DCF}Prefabs/Weapons/Rifles/AK74/Rifle_AK74.et" Count 2 }
ERR_SiteStock { Prefab "{BBB50A815A2F916B}Prefabs/Weapons/Magazines/Magazine_545x39_AK_30rnd_Ball.et" Count 10 }
ERR_SiteStock { Prefab "{243948B23D90BECB}Prefabs/Items/Equipment/Binoculars/Binoculars_B8/Binoculars_B8.et" Count 2 }
--- a/tests/test_ground_depot_provenance.py
+++ b/tests/test_ground_depot_provenance.py
@@ -5,14 +5,41 @@
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'
+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'
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('))
+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->')
@@ -26,26 +53,47 @@
#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 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;Array<IEntity>DepotMagazines;};
+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;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 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 Service{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++;}
+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(')+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;}
@@ -80,4 +128,4 @@
{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')
+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')
@@ -0,0 +1,19 @@
check: /tmp/err-policy-sgdi7y8u/check.cpp:211: int main(): Assertion `f.service.DepotMineNeeded()&&!f.service.DepotNeeded()' failed.
Traceback (most recent call last):
File "/tmp/erri-depot-mines-stock-1116/tests/test_ground_depot_provenance.py", line 50, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<77 lines>...
}
^
''')
^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-sgdi7y8u/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1 @@
PASS: actual Compatible and bounded stock preflight before report/claim consumption, later compatible truck, launcher controls, convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup
@@ -0,0 +1 @@
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
@@ -0,0 +1,30 @@
{
"scope": "SiteService, EveronSites.conf, InfantryResupply and existing ground-depot provenance test only; courier/lifecycle/root patches separate.",
"authored": "Five eligible nonopening OUTPOST definitions receive exactly2 PMN4 each at original startup (10 total in authored scenario). No changes to Southern opening cache, other site kinds, guard counts or resupply generation.",
"provenance": "Core captures exact spawned identity only for authored PMN4 resource, untargeted eligible outpost stock, max2 per camp and96 combined identities. Core has no AI class dependency. READY-only detached generic ItemCount/Item/Stock/Original APIs; magazine APIs preserved. No late inventory scan or refresh. Native inert readiness is checked by upper mine controller/transfer/final admission and root initial stock witness.",
"import_contract": "DepotMineAvailable preserves exact initial convoy batch via CaptureMines and closed initial allowance (max2 startup), then mine lifetime96 and shared outgoing spent96. DepotMineNeeded counts only nonnull committed identities already in retained mine allowance beyond distinct m_DepotMineImports; magazine commitments/null/deletion create no demand. Imported identities can later be spent through existing mine supply; zero original allowance cannot bootstrap demand. Final AdmitDepotMine rechecks needed, ReadyMine, typed DeliveredTo(...,true), actual inventory Contains and reciprocal owner-slot custody, then appends exactitem andincrementsmineimports once. Return maintenance remains DepotCurrent rather than demand/cap, so95to96 does notcancelreturn.",
"accounting": "m_Stock magazine allowance/import count unchanged; original batch still bound and never rewritten; m_MineStock represents startup+provedimports, Status now labels allowance rather than initial. Source depot spent and outgoing truck spent stay separate. No engineer grant/placement policy changes.",
"tests": {
"primary": "tests/test_ground_depot_provenance.py actual Core capture/accessors, CaptureMines, mine/magazine demand/import, DeliveredTo and finite reservation; native ReadyMine/slot graph boundaries doubled explicitly",
"green": "provenance-green.log (before final status-label-only change; extracted tested methods unchanged)",
"baseline_absence": "baseline-feature-absence.log fails explicit feature-presence assertion before compilation; no fake behavioral red claim",
"negative_control": "mutation-magazine-demand.log compiles then runtime-fails when mine demand counts magazine allowance",
"existing_planning": "planning-green.log, unchanged actual planning suite against proposed service including final status label",
"scope": "No fullsuite/native/target writes. Compiler sessions finished."
},
"peer_dependency": {
"loader": "/tmp/erri-depot-mines-courier-1116/addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c",
"loader_sha256": "6abb278409527bb67db084138a773de240d99ba7f63f9ac740f8167615d53f1d"
},
"logs": {
"provenance-green.log": "e26ca35beb9bdd2800680a97f46df2693145f8ed2d63e42c745c73e3238d2205",
"baseline-feature-absence.log": "ee96c32a45ad254096dd0046f4d4411d7c7d8b7b71a2e17754665abb23d4bcf4",
"mutation-magazine-demand.log": "ab0fe2ac580e35331683845aeb0b42f7b0e57bfd3840f5e1cba3be57557da5b1",
"planning-green.log": "c12aa5ba9b2141830d3916c3ab0285c06cbb44f606a9f8fbe42787ade0e67b21"
},
"owned_reference": {
"path": "/home/jakub/devel/escapists/escapists-ai/Scripts/Game/EscapistsAI/EAI_DepotLoading.c",
"read_only": true,
"review": "Original uses real porter cargo lines but creates stock/staff and calls Fill at Begin. This successor slice reuses physical responders and finite startup-only stock; does not copy refill/spawn behavior."
}
}
@@ -0,0 +1,25 @@
{
"files": {
"addons/core/Scripts/Game/ERR/00_Core/ERR_SiteService.c": {
"before_working_sha256": "210646e679056f4a46ef454a5abe15bd42d48349aa376ccfbb6b3b2ebeac6812",
"before_index_sha256": "9bd0824995d3e08cdb2745f1cb539ddd27e422509814188ea6ab2ba56631e7cd",
"proposed_sha256": "15a28876954bbbe0e80d5b5b19e7336f154023cd05b67d65dd709ae7fd9ac712"
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c": {
"before_working_sha256": "bf50a7a7d5cd182d29a02ea5de8d58b46979019158250c5ab589ed8ffe49b974",
"before_index_sha256": "966068555a6724e620a2c377b98d8dc02f141466c5cdd44636f86cb1cfe9ac78",
"proposed_sha256": "1bfc1079961eaecee0c1ebb434570aaeb359c008f9f309033b33582092557b35"
},
"addons/core/Configs/ERR/EveronSites.conf": {
"before_working_sha256": "b479000b36154a6a3af6b519b086251d981ef63cb86a973d00529499cfd4238c",
"before_index_sha256": "b479000b36154a6a3af6b519b086251d981ef63cb86a973d00529499cfd4238c",
"proposed_sha256": "a66c30039c7141e5a0f22af21aa522728fe4aa5cde821158aea3b716d11457a1"
},
"tests/test_ground_depot_provenance.py": {
"before_working_sha256": "33880dbf308dd4f338cc4e4bb04449d662cc2fe0941721a819528b7f7a6decd7",
"before_index_sha256": "33880dbf308dd4f338cc4e4bb04449d662cc2fe0941721a819528b7f7a6decd7",
"proposed_sha256": "a56a0402ee255e2824fc65ec5cc613088013e2795413e484161bab7e28f45c85"
}
},
"patch_sha256": "c7273b6800da3600cc49348000b139a5caaf50bc6395640d5ff59c674c0fdb43"
}