--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c +++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c @@ -221,6 +221,9 @@ bool accepted = m_Source.TryMoveItemToStorage(m_Mine, m_Storage, -1, m_Callback); if (!accepted && m_Acknowledgement != 1) return Fail("native mine move refused; exact identity remains spent"); } + // Native callbacks may synchronously stop this leaf or revoke its authority. + if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State; + if (!Context()) return Fail("original mine carrier or truck authority changed during native submission"); if (m_Acknowledgement < 0) return Fail("native mine move failed; unproved custody remains quarantined"); if (m_Acknowledgement == 1 && PhysicalProof()) { --- a/tests/test_infantry_mine_transfer.py +++ b/tests/test_infantry_mine_transfer.py @@ -101,6 +101,18 @@ void Move(){source.items.clear();destination.items.Insert(&incoming);from.attached=nullptr;to.attached=&incoming;inItem.slot=&to;} }; int main(){ + // Inline native completion cannot resurrect a cancelled transfer or bypass + // original authority, in either zero replacement or one-mine top-up mode. + for(int empty=0;empty<2;empty++)for(int loss=0;loss<5;loss++){ + Fixture f;if(empty){f.destination.items.clear();}auto t=f.Job(empty); + f.source.effect=[&]{if(loss==0)t.Stop();if(loss==1)f.mode.active=false;if(loss==2)f.roster.valid=false;if(loss==3)ERR_ConvoyRules::captured=true;if(loss==4){if(empty)f.mode.hq.designated=false;else f.destination.items.clear();}f.Move();f.source.last->OnComplete();}; + assert(t.Tick(0)==FAILED&&t.Submitted()&&!t.Unresolved()&&f.source.calls==1); + assert(t.Tick(1)==FAILED&&f.source.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());} + // A synchronous success callback without moved custody remains quarantined + // after cancellation; late proof settles it without resuming or refunding. + {Fixture f;auto t=f.Job();f.source.effect=[&]{t.Stop();f.source.last->OnComplete();}; + assert(t.Tick(0)==FAILED&&t.Unresolved()&&f.source.calls==1);f.Move(); + assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.source.calls==1&&f.spent.Count()==1&&!f.reservation.ReleaseUnsubmitted());} // Authoritative designation plus an inspected empty inventory permits exactly one finite replacement. {Fixture f;f.destination.items.clear();auto t=f.Job(true);assert(t.Tick(0)==PENDING&&t.Submitted());f.Move();f.source.last->OnComplete();assert(t.Tick(1)==SUCCEEDED&&f.destination.items.Count()==1&&f.source.calls==1);} for(int k=0;k<7;k++){Fixture f;f.destination.items.clear();