Files
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

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);}