Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
65 lines
4.7 KiB
Diff
65 lines
4.7 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
|
|
@@ -231,6 +231,9 @@
|
|
m_Submitted = true;
|
|
bool accepted = m_Carried.TrySwapItemStorages(m_Incoming, m_Loaded, m_Callback);
|
|
if (!accepted && m_Acknowledgement != 1) return Fail("native swap refused; exact committed identity will not be retried");
|
|
+ // Native inventory callbacks can revoke this operation before returning.
|
|
+ if (m_State != ERR_MountedAmmoTransferState.PENDING) return m_State;
|
|
+ if (!Context()) return Fail("original courier authority changed during native submission");
|
|
}
|
|
if (m_Acknowledgement < 0) return Fail("native swap failure; partial or unproved movement remains quarantined");
|
|
if (m_Acknowledgement == 1 && SwappedProof())
|
|
--- a/tests/test_mounted_ammo_transfer.py
|
|
+++ b/tests/test_mounted_ammo_transfer.py
|
|
@@ -108,6 +108,17 @@
|
|
void Swap(){fromSlot.attached=&loaded;toSlot.attached=&incoming;oldItem.slot=&fromSlot;newItem.slot=&toSlot;muzzle.mag=&newMag;carried.items={&loaded};}
|
|
};
|
|
int main(){
|
|
+ // Inline completion must not resurrect a cancelled transaction or stale authority.
|
|
+ for(int loss=0;loss<4;loss++){Fixture f;auto t=f.Job();
|
|
+ f.carried.ack=1;f.carried.effect=[&](){if(loss==0)t.Stop();if(loss==1)f.task.Stopped=true;if(loss==2)f.mode.active=false;if(loss==3)f.target.Released=true;f.Swap();};
|
|
+ assert(t.Tick(0)==FAILED&&t.Submitted()&&!t.Unresolved());
|
|
+ assert(t.Tick(1)==FAILED&&f.carried.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());}
|
|
+ // Cancellation with acknowledgement but no custody keeps quarantine; later exact
|
|
+ // movement settles uncertainty without success, another submission or a refund.
|
|
+ {Fixture f;auto t=f.Job();f.carried.ack=1;f.carried.effect=[&](){t.Stop();};
|
|
+ assert(t.Tick(0)==FAILED&&t.Unresolved());f.Swap();assert(!t.Unresolved());
|
|
+ assert(t.Tick(1)==FAILED&&f.carried.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());}
|
|
+
|
|
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved());assert(f.carried.calls==1);
|
|
f.carried.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Swap();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());
|
|
assert(t.Tick(9000)==SUCCEEDED&&f.carried.calls==1&&f.spent.Count()==1&&f.oldMag.rounds==25&&f.newMag.rounds==100);}
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoPickup.c
|
|
@@ -220,6 +220,9 @@
|
|
m_Submitted = true;
|
|
bool accepted = m_TruckInventory.TryMoveItemToStorage(m_Incoming, m_Destination, -1, m_Callback);
|
|
if (!accepted && m_Acknowledgement != 1) return Fail("native pickup refused; exact stock remains spent");
|
|
+ // Native inventory callbacks can revoke this operation before returning.
|
|
+ if (m_State != ERR_MountedAmmoTransferState.PENDING) return m_State;
|
|
+ if (!Context()) return Fail("original courier authority changed during native submission");
|
|
}
|
|
if (m_Acknowledgement < 0) return Fail("native pickup failed; unproved movement remains quarantined");
|
|
if (m_Acknowledgement == 1 && PhysicalProof())
|
|
--- a/tests/test_mounted_ammo_pickup.py
|
|
+++ b/tests/test_mounted_ammo_pickup.py
|
|
@@ -113,6 +113,17 @@
|
|
void Move(){fromSlot.attached=nullptr;carrySlot.attached=&incoming;newItem.slot=&carrySlot;truckInventory.items.clear();carried.items={&incoming};}
|
|
};
|
|
int main(){
|
|
+ // Inline completion must not resurrect a cancelled transaction or stale authority.
|
|
+ for(int loss=0;loss<4;loss++){Fixture f;auto t=f.Job();
|
|
+ f.truckInventory.ack=1;f.truckInventory.effect=[&](){if(loss==0)t.Stop();if(loss==1)f.task.Stopped=true;if(loss==2)f.mode.active=false;if(loss==3)f.target.Released=true;f.Move();};
|
|
+ assert(t.Tick(0)==FAILED&&t.Submitted()&&!t.Unresolved());
|
|
+ assert(t.Tick(1)==FAILED&&f.truckInventory.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());}
|
|
+ // Cancellation with acknowledgement but no custody keeps quarantine; later exact
|
|
+ // movement settles uncertainty without success, another submission or a refund.
|
|
+ {Fixture f;auto t=f.Job();f.truckInventory.ack=1;f.truckInventory.effect=[&](){t.Stop();};
|
|
+ assert(t.Tick(0)==FAILED&&t.Unresolved());f.Move();assert(!t.Unresolved());
|
|
+ assert(t.Tick(1)==FAILED&&f.truckInventory.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());}
|
|
+
|
|
// No personal firearm exists in the fixture: compatibility is the pinned mounted muzzle.
|
|
{Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved());assert(f.truckInventory.calls==1&&f.reservation.CommittedFor(&f.incoming));
|
|
f.truckInventory.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Move();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());
|