Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
83 lines
6.1 KiB
Plaintext
83 lines
6.1 KiB
Plaintext
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c
|
|
@@ -188,6 +188,9 @@
|
|
bool Unresolved()
|
|
{
|
|
if (!m_Submitted) return false;
|
|
+ // A deleted exact item cannot complete this one-item move. Its committed
|
|
+ // reservation stays spent; capture or damage alone does not settle custody.
|
|
+ if (!m_Incoming) return false;
|
|
if (m_Acknowledgement == 1 && PhysicalProof()) return false;
|
|
if (m_Acknowledgement < 0 && OriginalProof()) return false;
|
|
// False native return without a definitive callback is also quarantined.
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
|
|
@@ -181,6 +181,9 @@
|
|
bool Unresolved()
|
|
{
|
|
if (!m_Submitted) return false;
|
|
+ // Both exchange identities must be deleted before loss alone settles this
|
|
+ // two-item operation. Either surviving item may still be in flight.
|
|
+ if (!m_Incoming && !m_Loaded) return false;
|
|
if (m_Acknowledgement == 1 && SwappedProof()) return false;
|
|
if (m_Acknowledgement < 0 && OriginalProof()) return false;
|
|
// Even a false submission result without callback cannot authorize another
|
|
--- a/tests/test_mounted_ammo_pickup.py
|
|
+++ b/tests/test_mounted_ammo_pickup.py
|
|
@@ -133,6 +133,19 @@
|
|
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Move();f.truckInventory.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.truckInventory.calls==1);}
|
|
{Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved()&&f.truckInventory.calls==1);}
|
|
{Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&t.Unresolved()&&f.truckInventory.calls==1);}
|
|
+ // Native deletion clears the weak exact identity: no item can arrive later.
|
|
+ // Retain the spent slot, do not count delivery, and never retry on late callbacks.
|
|
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Unresolved());
|
|
+ t.m_Incoming=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;
|
|
+ f.fromSlot.attached=nullptr;f.truckInventory.items.clear();
|
|
+ assert(t.Tick(1)==FAILED&&!t.Unresolved()&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());
|
|
+ f.truckInventory.last->OnComplete();f.truckInventory.last->OnFailed();
|
|
+ assert(t.Tick(2)==FAILED&&!t.Unresolved()&&f.truckInventory.calls==1&&f.spent.Count()==1);}
|
|
+ // Damage, worker loss, source capture and a moved-but-existing item remain uncertain.
|
|
+ for(int k=0;k<4;k++){Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);
|
|
+ if(k==0){f.incoming.alive=false;}if(k==1){t.m_Worker=nullptr;}
|
|
+ if(k==2){f.donor.Released=true;}if(k==3){f.truckInventory.items.clear();f.newItem.slot=nullptr;}
|
|
+ t.Stop();assert(t.Unresolved()&&t.Tick(1)==FAILED&&f.truckInventory.calls==1);}
|
|
// Original identity, carried capacity and source access losses fail before any submission.
|
|
for(int k=0;k<43;k++){Fixture f;auto t=f.Job();
|
|
switch(k){case 0:f.task.Stopped=true;break;case 1:f.task.Ready=false;break;case 2:f.donor.Released=true;break;case 3:f.target.Released=true;break;
|
|
--- a/tests/test_mounted_ammo_transfer.py
|
|
+++ b/tests/test_mounted_ammo_transfer.py
|
|
@@ -126,6 +126,16 @@
|
|
{Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Swap();f.carried.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.carried.calls==1);}
|
|
{Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved()&&f.carried.calls==1);}
|
|
{Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&f.carried.calls==1);}
|
|
+ // A swap retains quarantine while either exact exchange identity survives.
|
|
+ for(int side=0;side<2;side++){Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Unresolved());
|
|
+ if(side==0)t.m_Incoming=nullptr;else t.m_Loaded=nullptr;
|
|
+ t.Stop();assert(t.Unresolved()&&t.Tick(1)==FAILED&&f.carried.calls==1);
|
|
+ f.carried.last->OnComplete();assert(t.Unresolved());
|
|
+ t.m_Incoming=nullptr;t.m_Loaded=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;
|
|
+ assert(!t.Unresolved()&&!f.reservation.ReleaseUnsubmitted()&&f.spent.Count()==1);
|
|
+ f.carried.last->OnFailed();assert(t.Tick(2)==FAILED&&!t.Unresolved()&&f.carried.calls==1);}
|
|
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.incoming.alive=false;f.loaded.alive=false;
|
|
+ t.Stop();assert(t.Unresolved()&&f.carried.calls==1);}
|
|
// Physical target changes, outgoing capacity/content guards, source inventory bounds.
|
|
for(int k=0;k<34;k++){Fixture f;auto t=f.Job();
|
|
switch(k){case 0:f.task.Stopped=true;break;case 1:f.task.Ready=false;break;case 2:f.donor.Released=true;break;case 3:f.target.Released=true;break;
|
|
--- a/tests/test_mounted_resupply.py
|
|
+++ b/tests/test_mounted_resupply.py
|
|
@@ -125,6 +125,14 @@
|
|
// Uncertain pickup survives Stop and keeps both matching claims until callback/proof settles.
|
|
{Fixture f;f.Dispatch();f.c.Tick(2000);assert(f.c.m_State==4);f.c.m_Pickup->unresolved=true;auto*p=f.c.m_Pickup;f.c.Stop();
|
|
assert(f.c.m_State==8&&f.c.Busy()&&f.hq.tokens.size()==2&&f.spent.Count()==1);p->unresolved=false;f.c.Tick(3000);assert(!f.c.Busy()&&f.hq.tokens.empty()&&f.spent.Count()==1);}
|
|
+ // A terminally lost submitted pickup releases holds, retaining spent identity.
|
|
+ // Leaf deletion semantics are exercised in the actual pickup/swap regressions.
|
|
+ {Fixture f;Entity surviving;f.stock.Insert(&surviving);f.Dispatch();f.c.Tick(2000);auto*p=f.c.m_Pickup;assert(p&&f.c.m_State==4);
|
|
+ p->unresolved=true;f.c.workerValid=false;f.c.Tick(3000);
|
|
+ assert(f.c.m_State==8&&f.hq.tokens.size()==2&&f.truck.SupplyHeld&&f.target.SupplyHeld);
|
|
+ p->unresolved=false;f.c.Tick(4000);assert(!f.c.Busy()&&f.hq.tokens.empty()&&!f.truck.SupplyHeld&&!f.target.SupplyHeld&&f.spent.Count()==1);
|
|
+ f.c.m_Incoming=&surviving;f.c.workerValid=true;f.demand.MessageId=2;f.demand.ObservedAt=f.status.ObservedAt=200000;
|
|
+ assert(f.Begin(200000)&&f.spent.Count()==2&&f.c.m_Trip==2);}
|
|
// Uncertain swap is not released merely because the controller times out or is stopped.
|
|
{Fixture f;f.Pickup();f.c.m_Swap->result=0;f.c.Tick(3000);f.c.m_Swap->unresolved=true;auto*t=f.c.m_Swap;f.c.Stop();
|
|
assert(f.c.m_State==8&&f.c.m_Swap==t&&f.hq.tokens.size()==2);t->unresolved=false;f.c.Tick(4000);assert(!f.c.Busy()&&f.spent.Count()==1);}
|