Files
EscapistsReintroduced/docs/evidence/mortar-deleted-shell-2026-10-02/fix.patch
T
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

72 lines
4.4 KiB
Diff

--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSupplyTransfer.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MortarSupplyTransfer.c
@@ -81,6 +81,13 @@
ERR_MortarSupplyState Poll(float now)
{
if (!Replication.IsServer()) return ERR_MortarSupplyState.FAILED;
+ // The exact deleted shell cannot complete later. Match courier settlement:
+ // release only uncertainty, never authorize a shot or retry this lost item.
+ if (!m_Shell)
+ {
+ m_Resolved = true; m_Invalidated = true; m_Ready = false;
+ return ERR_MortarSupplyState.FAILED;
+ }
bool valid = Valid(now);
bool sourceContains = Contains(m_Source);
bool destinationContains = Contains(m_Destination);
--- a/tests/test_mortar_transactions.py
+++ b/tests/test_mortar_transactions.py
@@ -1,7 +1,9 @@
"""Actual Enforce policies and transaction observation body under narrow API fakes."""
from policy_harness import ROOT, body, run
+from pathlib import Path
+import os
r=ROOT/'addons/core/Scripts/Game/ERR/10_AI'
-readiness=r/'ERR_MortarReadiness.c'; transfer=r/'ERR_MortarSupplyTransfer.c'; battery=r/'ERR_MortarBattery.c'
+readiness=r/'ERR_MortarReadiness.c'; transfer=Path(os.environ.get('ERR_MORTAR_SUPPLY_TRANSFER_SOURCE',r/'ERR_MortarSupplyTransfer.c')); battery=r/'ERR_MortarBattery.c'
policy='\n'.join([body(readiness,'static ERR_MortarSupplyState Population('),body(readiness,'static bool AllocationValid('),body(readiness,'static int CycleGate('),body(transfer,'static bool Authority('),body(transfer,'static bool Proven(')])
policy=policy.replace('ERR_MortarSupplyState.','ERR_MortarSupplyState::')
run('''#include <cassert>
@@ -44,6 +46,7 @@
poll=poll.replace('ERR_MortarSupplyState.','ERR_MortarSupplyState::').replace('Replication.IsServer()','Replication::IsServer()').replace('InventoryItemComponent item;','InventoryItemComponent* item=nullptr;').replace('if (m_Shell) item = InventoryItemComponent.Cast(m_Shell.FindComponent(InventoryItemComponent));','if (m_Shell) item = m_Shell;').replace('item.GetParentSlot().GetStorage()','item->GetParentSlot()->GetStorage()').replace('item.GetParentSlot()','item->GetParentSlot()')
ack=body(transfer,'void Acknowledge(')
run('''#include <cassert>
+#include <initializer_list>
enum class ERR_MortarSupplyState {FAILED=-1,PENDING,READY};
struct Replication { static bool IsServer(){return true;} };
struct Slot { void* storage; void* GetStorage(){return storage;} };
@@ -57,7 +60,10 @@
Slot slot{this}; InventoryItemComponent item{&slot}; InventoryItemComponent* m_Shell=&item;
bool Valid(float){return valid&&!m_Invalidated;}
bool Contains(int which){return m_Shell && (which==1 ? source : destination);}
-'''+ack+poll+'''
+'''+ack+body(transfer,'bool Unresolved(')+poll+'''
+};
+struct Battery {Transfer* m_Transfer=nullptr;
+'''+body(battery,'bool SupplyPending(').replace('m_Transfer.Unresolved()', 'm_Transfer->Unresolved()')+'''
};
int main(){
using S=ERR_MortarSupplyState;
@@ -76,8 +82,19 @@
assert(late.Poll(8000)==S::FAILED); // no deadline-boundary success bypass
Transfer failed;failed.Acknowledge(false);failed.Acknowledge(true);
assert(failed.Poll(1)==S::FAILED && failed.m_Acknowledgement==-1 && failed.m_Resolved);
- Transfer gone;gone.m_Shell=nullptr;gone.Acknowledge(true);
- assert(gone.Poll(1)==S::PENDING && !gone.m_Resolved); // ambiguous until deadline, never another move
+ // Exact deletion is permanent loss, not an operation that can still arrive.
+ for(int ack=0;ack<3;ack++)for(bool invalidated:{false,true}){
+ Transfer gone;Battery battery;battery.m_Transfer=&gone;gone.m_Invalidated=invalidated;assert(gone.Unresolved()&&battery.SupplyPending());gone.m_Shell=nullptr;
+ if(ack==1)gone.Acknowledge(true);
+ if(ack==2)gone.Acknowledge(false);
+ assert(gone.Poll(1)==S::FAILED&&gone.m_Resolved&&gone.m_Invalidated&&!gone.m_Ready&&!gone.Unresolved());
+ assert(!battery.SupplyPending());
+ gone.Acknowledge(true);assert(gone.Poll(90000)==S::FAILED&&!gone.Unresolved()&&!gone.m_Ready);
+ }
+ // A surviving detached shell, missing inventory or lost authority remains
+ // uncertain; only exact identity deletion permits unconditional settlement.
+ Transfer detached;detached.source=false;detached.destination=false;detached.Acknowledge(true);
+ assert(detached.Poll(1)==S::PENDING&&detached.Unresolved());assert(detached.Poll(8000)==S::FAILED&&detached.Unresolved());
Transfer unauthorized;unauthorized.valid=false;
assert(unauthorized.Poll(0)==S::FAILED && !unauthorized.m_Resolved);
}