Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
265 lines
19 KiB
Plaintext
265 lines
19 KiB
Plaintext
--- /dev/null
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_ConvoyMineStock.c
|
|
@@ -0,0 +1,108 @@
|
|
+// Optional initial stock only. Spawn callbacks supply exact identities; these
|
|
+// never enter the magazine readiness barrier or acquire a separate spent ledger.
|
|
+class ERR_ConvoyMineCargo : ScriptedInventoryOperationCallback
|
|
+{
|
|
+ bool Finished;
|
|
+ bool Succeeded;
|
|
+ IEntity Item;
|
|
+ protected void Finish(bool success)
|
|
+ {
|
|
+ if (Finished) return;
|
|
+ Finished = true; Succeeded = success;
|
|
+ if (!success) return;
|
|
+ // GetItem is supported for Spawn, unlike the later Move/Swap operations.
|
|
+ RplComponent replicated = RplComponent.Cast(Replication.FindItem(GetItem()));
|
|
+ if (replicated) Item = replicated.GetEntity();
|
|
+ }
|
|
+ void Refused() { Finish(false); }
|
|
+ protected override void OnComplete() { Finish(true); }
|
|
+ protected override void OnAllComplete() { Finish(true); }
|
|
+ protected override void OnFailed() { Finish(false); }
|
|
+ protected override void OnAllFailed() { Finish(false); }
|
|
+}
|
|
+
|
|
+class ERR_ConvoyMineStock
|
|
+{
|
|
+ protected ERR_GameMode m_Mode;
|
|
+ protected ERR_SupplyConvoyTask m_Task;
|
|
+ protected ERR_ConvoyVehicle m_Original;
|
|
+ protected IEntity m_Truck;
|
|
+ protected InventoryStorageManagerComponent m_Inventory;
|
|
+ protected ref array<ref ERR_ConvoyMineCargo> m_Cargo = {};
|
|
+ protected ref array<IEntity> m_Items = {};
|
|
+ protected ref array<IEntity> m_Before = {};
|
|
+ protected float m_Deadline = -1;
|
|
+ protected float m_LastTick = -1;
|
|
+ protected bool m_Attempted;
|
|
+ protected bool m_Closed;
|
|
+ void ERR_ConvoyMineStock(ERR_GameMode mode, ERR_SupplyConvoyTask task)
|
|
+ {
|
|
+ m_Mode = mode; m_Task = task;
|
|
+ if (!task || task.Vehicles.Count() != 6) return;
|
|
+ m_Original = task.Vehicles[1];
|
|
+ if (!m_Original) return;
|
|
+ m_Truck = m_Original.Entity;
|
|
+ if (m_Truck) m_Inventory = InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent));
|
|
+ }
|
|
+ // These are initial identities, not a live stock count or transfer authority.
|
|
+ int Count() { return m_Items.Count(); }
|
|
+ IEntity At(int index)
|
|
+ {
|
|
+ if (index < 0 || index >= m_Items.Count()) return null;
|
|
+ return m_Items[index];
|
|
+ }
|
|
+ bool Complete() { return m_Closed; }
|
|
+ void Stop() { m_Closed = true; m_Before.Clear(); }
|
|
+ protected bool Current(bool ownerValid)
|
|
+ {
|
|
+ if (!ownerValid || !Replication.IsServer() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active()) return false;
|
|
+ if (!m_Task || m_Task.Stopped || !m_Task.Ready || m_Task.Vehicles.Count() != 6 || m_Task.Vehicles[1] != m_Original) return false;
|
|
+ if (!m_Original || m_Original.Entity != m_Truck || !m_Truck || !m_Inventory || m_Inventory.GetOwner() != m_Truck) return false;
|
|
+ return m_Truck.FindComponent(InventoryStorageManagerComponent) == m_Inventory;
|
|
+ }
|
|
+ protected bool ReadyItem(IEntity item, array<IEntity> items)
|
|
+ {
|
|
+ if (!item || m_Before.Contains(item) || !items.Contains(item) || !item.GetPrefabData()
|
|
+ || item.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) return false;
|
|
+ InventoryItemComponent carried = InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent));
|
|
+ if (!carried || !carried.GetParentSlot() || carried.GetParentSlot().GetAttachedEntity() != item
|
|
+ || !ERR_InfantryAmmoTransfer.StoredUnder(item, m_Truck)) return false;
|
|
+ if (!ERR_Actors.Alive(item) || !item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)) return false;
|
|
+ SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(item.FindComponent(SCR_PressureTriggerComponent));
|
|
+ return trigger && !trigger.IsActivated() && !trigger.IsActivatedByDefault();
|
|
+ }
|
|
+ void Tick(float now, bool ownerValid)
|
|
+ {
|
|
+ if (m_Closed) return;
|
|
+ if (!Current(ownerValid) || !ERR_ForceRules.Finite(now) || now < 0 || now < m_LastTick) { Stop(); return; }
|
|
+ m_LastTick = now;
|
|
+ if (m_Deadline < 0) m_Deadline = now + 90000;
|
|
+ if (now >= m_Deadline) { Stop(); return; }
|
|
+ if (!m_Attempted)
|
|
+ {
|
|
+ if (!m_Task.CargoIssued || !m_Task.MagazinesIssued) return;
|
|
+ int primary = m_Task.Cargo.Count();
|
|
+ if (primary > 128) { Stop(); return; }
|
|
+ foreach (ERR_ConvoyCargoCallback operation : m_Task.Cargo)
|
|
+ if (!operation || !operation.Finished) return;
|
|
+ // All primary insertions have settled before measuring physical headroom.
|
|
+ // A refused optional request still spends one of the two initial attempts.
|
|
+ m_Attempted = true;
|
|
+ ERR_Inventory.Items(m_Inventory, m_Before);
|
|
+ if (m_Before.Count() > 128 || !ERR_EnemyLoadoutCatalog.Available(ERR_EnemyLoadoutCatalog.MINE)) { Stop(); return; }
|
|
+ for (int index = 0; index < 2 && index < 128 - m_Before.Count() && index < 128 - primary; index++)
|
|
+ {
|
|
+ ERR_ConvoyMineCargo load = new ERR_ConvoyMineCargo(); m_Cargo.Insert(load);
|
|
+ if (!m_Inventory.TrySpawnPrefabToStorage(ERR_EnemyLoadoutCatalog.MINE, null, -1, EStoragePurpose.PURPOSE_DEPOSIT, load)) load.Refused();
|
|
+ }
|
|
+ }
|
|
+ foreach (ERR_ConvoyMineCargo pending : m_Cargo) if (!pending.Finished) return;
|
|
+ array<IEntity> items = {}; ERR_Inventory.Items(m_Inventory, items);
|
|
+ // One detached freeze: missing, captured or unsupported native identities
|
|
+ // are omitted, never adopted later. Failed/late operations are never retried.
|
|
+ if (items.Count() <= 128)
|
|
+ foreach (ERR_ConvoyMineCargo completed : m_Cargo)
|
|
+ if (completed.Succeeded && ReadyItem(completed.Item, items) && !m_Items.Contains(completed.Item)) m_Items.Insert(completed.Item);
|
|
+ Stop();
|
|
+ }
|
|
+}
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyService.c
|
|
@@ -144,6 +144,7 @@
|
|
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(task);
|
|
if (!supply || supply.CargoIssued || supply.Stopped) return;
|
|
supply.CargoIssued = true;
|
|
+ supply.MineStock = new ERR_ConvoyMineStock(m_Mode, supply);
|
|
InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(supply.Vehicles[1].Entity.FindComponent(InventoryStorageManagerComponent));
|
|
if (!inventory) { supply.MagazinesIssued = true; return; }
|
|
ERR_Inventory.Items(inventory, supply.CargoBefore);
|
|
@@ -266,6 +267,7 @@
|
|
task.Ready = true; IssueCargo(task);
|
|
}
|
|
IssueMagazines(task, now);
|
|
+ if (task.MineStock) task.MineStock.Tick(now, MountedStockOwner(task, 1, ERR_HQ.Cast(m_Mode.Command())));
|
|
foreach (ERR_ConvoyVehicle checked : task.Vehicles)
|
|
if (!checked.Released && !checked.CrewValid(m_Mode)) checked.Release(m_Mode);
|
|
if (task.Tactics && task.Tactics.Tick(now)) continue;
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_SupplyConvoyTactics.c
|
|
@@ -176,10 +176,12 @@
|
|
bool MagazinesIssued;
|
|
ref array<ref ERR_MountedMagazineSeed> MountedStock = {};
|
|
ref array<IEntity> CargoBefore = {};
|
|
+ ref ERR_ConvoyMineStock MineStock;
|
|
override void Stop(ERR_GameMode mode, string reason)
|
|
{
|
|
if (Stopped) return;
|
|
if (Tactics) Tactics.Stop();
|
|
+ if (MineStock) MineStock.Stop();
|
|
foreach (ERR_SupplyTeam team : Teams) team.Release(mode);
|
|
super.Stop(mode, reason);
|
|
}
|
|
--- /dev/null
|
|
+++ b/tests/test_convoy_mine_stock.py
|
|
@@ -0,0 +1,117 @@
|
|
+"""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')
|