Capture current mission, AI, map and administration development

Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
2026-10-13 22:52:53 +02:00
parent a951a96edc
commit 6a667d8deb
8104 changed files with 2124945 additions and 2061 deletions
@@ -0,0 +1,47 @@
PROPOSAL ONLY; no target edits or native execution.
New leaf and actual-method test only. Parent registers test_infantry_mine_transfer.py.
API:
ERR_InfantryMineTransfer(mode, Vehicle truck, SCR_AIGroup truckGroup, IEntity actor,
SCR_AIGroup recipientGroup, IEntity incoming, ERR_FiniteMagazineReservation reservation,
ERR_HQStatusSampler originalRoster, int originalSlot)
Tick(now): ERR_InfantryAmmoTransferState (-1 failed,0 pending,1 succeeded).
Submitted(), Unresolved(), Stop(), Reason().
static ReadyMine(item): exact alive inactive native PMN4.
static Carrier(actor,group,exclude=null): exact sole usable PMN4, or null when
zero/multiple/unknown. Optional exclusion is internal after submission only.
Coordinator must pass same original roster slot and shared finite reservation
from captured mine originals + EXISTING spent ledger. Leaf pins driver, physical
records, managers, reciprocal original source slot and exact original carried mine
at construction. It never selects another carrier or swaps carrier entitlement.
One native Move submission only, Commit before call; callback identities are not
queried (GetItem unsupported for Move). Callback+exact reciprocal storage and
source absence prove delivery. Unknown/partial movement remains unresolved after
Stop or timeout; definitive callback-failure plus unchanged source can settle it.
Deleted incoming entity settles this one-item transaction; damage/capture does not.
No rollback, spawn, new capability at zero stock, placement-latch reset or refund.
Tests execute production constructor, Carrier, Context, Tick, Stop, Unresolved,
callbacks, custody and shared reservation with native doubles. They cover one/two/
zero/unknown carrier inventories, original actor/slot/driver/ledger/managers, native
capacity/access refusal, exact shared stock, inline callback with false return,
unknown false return, timeout/backwards clock, late proof, changed ownership and
exact deletion. Negative controls remove single-carrier or missing-damage guard;
both compile and assertion-fail. green.log passes.
No actual native inventory movement, pathfinding, multiplayer or Enforce lifetime
qualification claimed. Existing coordinator owns exclusive HQ reservations/radio.
Verified local SDK references:
- /tmp/erri-vanilla-stock/scripts/Game/generated/InventorySystem/InventoryStorageManagerComponent.c:
32 CanMoveItemToStorage,34 TryMoveItemToStorage(item,to,-1,callback),45 GetStorages,
47 GetItems,51 FindStorageForItem; GetOwner at14.
- /tmp/erri-vanilla-stock/scripts/Game/generated/InventorySystem/InventoryTask/ScriptedInventoryOperationCallback.c:
GetItem documentation explicitly excludes Move; all four callback signatures verified.
- /tmp/erri-vanilla-stock/scripts/Game/Weapon/Explosives/SCR_BaseTriggerComponent.c:
IsActivated22, IsActivatedByDefault29, both read-only native script state.
- Existing ERR_InfantryAmmoTransfer supplies verified group/driver/physics and
bounded StoredUnder ancestry patterns; actual StoredUnder extracted into test.
- ERR_Actors.Alive requires DamageManagerComponent (ERR_Actors.c:3-7); native
double matches this so unknown damage cannot masquerade as known unavailable stock.
@@ -0,0 +1,360 @@
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c
@@ -0,0 +1,217 @@
+class ERR_InfantryMineCallback : ScriptedInventoryOperationCallback
+{
+ ERR_InfantryMineTransfer Transfer;
+ protected override void OnComplete() { if (Transfer) Transfer.Acknowledge(true); }
+ protected override void OnAllComplete() { if (Transfer) Transfer.Acknowledge(true); }
+ protected override void OnFailed() { if (Transfer) Transfer.Acknowledge(false); }
+ protected override void OnAllFailed() { if (Transfer) Transfer.Acknowledge(false); }
+}
+
+// One finite original PMN4 tops up an existing original carrier from one to two.
+// The coordinator owns radio authorization and the shared truck/recipient claim.
+class ERR_InfantryMineTransfer
+{
+ protected ERR_GameMode m_Mode;
+ protected Vehicle m_Donor;
+ protected SCR_AIGroup m_DonorGroup;
+ protected IEntity m_Recipient;
+ protected SCR_AIGroup m_RecipientGroup;
+ protected IEntity m_Mine;
+ protected IEntity m_CarriedMine;
+ protected IEntity m_Driver;
+ protected ref ERR_ForceRecord m_DonorRecord;
+ protected ref ERR_ForceRecord m_RecipientRecord;
+ protected ref ERR_HQStatusSampler m_Roster;
+ protected int m_Slot;
+ protected int m_Strength;
+ protected InventoryStorageManagerComponent m_Source;
+ protected InventoryStorageManagerComponent m_Destination;
+ protected InventoryStorageSlot m_FromSlot;
+ protected BaseInventoryStorageComponent m_FromStorage;
+ protected BaseInventoryStorageComponent m_Storage;
+ protected ref ERR_FiniteMagazineReservation m_Reservation;
+ protected ref ERR_InfantryMineCallback m_Callback;
+ protected bool m_Submitted;
+ protected int m_Acknowledgement;
+ protected float m_Started = -1;
+ protected float m_LastTick = -1;
+ protected ERR_InfantryAmmoTransferState m_State = ERR_InfantryAmmoTransferState.PENDING;
+ protected string m_Reason;
+
+ void ERR_InfantryMineTransfer(ERR_GameMode mode, Vehicle donorVehicle, SCR_AIGroup donorGroup, IEntity recipientActor, SCR_AIGroup recipientGroup, IEntity exactMine, ERR_FiniteMagazineReservation reservation, ERR_HQStatusSampler originalRoster, int originalSlot)
+ {
+ m_Mode = mode; m_Donor = donorVehicle; m_DonorGroup = donorGroup;
+ m_Recipient = recipientActor; m_RecipientGroup = recipientGroup; m_Mine = exactMine;
+ m_Reservation = reservation; m_Roster = originalRoster; m_Slot = originalSlot;
+ if (!mode || !mode.Forces() || !donorVehicle || !recipientActor || !originalRoster) return;
+ m_DonorRecord = mode.Forces().FindGroup(donorGroup); m_RecipientRecord = mode.Forces().FindGroup(recipientGroup);
+ m_Strength = originalRoster.InitialStrength();
+ m_Source = InventoryStorageManagerComponent.Cast(donorVehicle.FindComponent(InventoryStorageManagerComponent));
+ m_Destination = InventoryStorageManagerComponent.Cast(recipientActor.FindComponent(InventoryStorageManagerComponent));
+ SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(donorVehicle.FindComponent(SCR_AIVehicleUsageComponent));
+ if (usage && usage.GetPilotCompartmentSlot()) m_Driver = usage.GetPilotCompartmentSlot().GetOccupant();
+ m_CarriedMine = Carrier(recipientActor, recipientGroup);
+ if (!exactMine) return;
+ InventoryItemComponent item = InventoryItemComponent.Cast(exactMine.FindComponent(InventoryItemComponent));
+ if (item) m_FromSlot = item.GetParentSlot();
+ if (m_FromSlot) m_FromStorage = m_FromSlot.GetStorage();
+ }
+ static bool ReadyMine(IEntity item)
+ {
+ if (!ERR_Actors.Alive(item) || !item.GetPrefabData() || item.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) return false;
+ if (!item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)) return false;
+ SCR_PressureTriggerComponent trigger = SCR_PressureTriggerComponent.Cast(item.FindComponent(SCR_PressureTriggerComponent));
+ return trigger && !trigger.IsActivated() && !trigger.IsActivatedByDefault();
+ }
+ // Exclusion is used only after submission: the incoming item may already be
+ // carried, but the exact pre-existing one must still prove carrier entitlement.
+ static IEntity Carrier(IEntity actor, SCR_AIGroup group, IEntity exclude = null)
+ {
+ if (!Replication.IsServer() || !group || group.IsSlave() || group.GetPlayerCount(true) > 0) return null;
+ if (!ERR_Actors.CanFight(actor) || ERR_Actors.Player(actor) || ERR_Actors.Group(actor) != group
+ || CompartmentAccessComponent.GetVehicleIn(actor)) return null;
+ InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(actor.FindComponent(InventoryStorageManagerComponent));
+ if (!inventory || inventory.GetOwner() != actor) return null;
+ array<IEntity> items = {}; inventory.GetItems(items);
+ if (items.Count() > 128) return null;
+ array<IEntity> seen = {}; IEntity found;
+ foreach (IEntity item : items)
+ {
+ if (!item || !item.GetPrefabData()) return null;
+ if (seen.Contains(item)) continue;
+ seen.Insert(item);
+ if (item == exclude || item.GetPrefabData().GetPrefabName() != ERR_EnemyLoadoutCatalog.MINE) continue;
+ InventoryItemComponent carried = InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent));
+ if (!carried || !carried.GetParentSlot() || carried.GetParentSlot().GetAttachedEntity() != item
+ || !ERR_InfantryAmmoTransfer.StoredUnder(item, actor)) return null;
+ if (!item.FindComponent(SCR_AntiPersonnelMineCollisionHandlerComponent)
+ || !item.FindComponent(SCR_PressureTriggerComponent) || !item.FindComponent(DamageManagerComponent)) return null;
+ if (!ReadyMine(item)) continue;
+ if (found) return null;
+ found = item;
+ }
+ return found;
+ }
+ bool Submitted() { return m_Submitted; }
+ string Reason() { return m_Reason; }
+ void Acknowledge(bool success)
+ {
+ if (!m_Submitted) return;
+ if (!success) m_Acknowledgement = -1;
+ else if (m_Acknowledgement == 0) m_Acknowledgement = 1;
+ }
+ protected ERR_InfantryAmmoTransferState Fail(string reason)
+ {
+ if (m_State == ERR_InfantryAmmoTransferState.PENDING) { m_State = ERR_InfantryAmmoTransferState.FAILED; m_Reason = reason; }
+ return m_State;
+ }
+ void Stop() { Fail("mine delivery authority stopped; submitted movement remains spent and observable"); }
+ protected bool ManagedStorage(InventoryStorageManagerComponent inventory, BaseInventoryStorageComponent storage, IEntity owner)
+ {
+ if (!inventory || !storage || !owner || inventory.GetOwner() != owner) return false;
+ if (!ERR_InfantryAmmoTransfer.StoredUnder(storage.GetOwner(), owner)) return false;
+ array<BaseInventoryStorageComponent> storages = {}; inventory.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
+ return storages.Count() <= 64 && storages.Contains(storage);
+ }
+ protected int Ownership(InventoryStorageManagerComponent inventory)
+ {
+ if (!inventory || !m_Mine) return -1;
+ array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
+ if (items.Count() > 128) return -1;
+ if (items.Contains(m_Mine)) return 1;
+ return 0;
+ }
+ protected bool InSlot(InventoryStorageSlot slot)
+ {
+ if (!m_Mine || !slot || slot.GetAttachedEntity() != m_Mine) return false;
+ InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
+ return item && item.GetParentSlot() == slot;
+ }
+ protected bool OriginalProof()
+ {
+ return m_FromSlot && m_FromSlot.GetStorage() == m_FromStorage
+ && ManagedStorage(m_Source, m_FromStorage, m_Donor) && InSlot(m_FromSlot)
+ && m_Destination && m_Destination.GetOwner() == m_Recipient
+ && Ownership(m_Source) == 1 && Ownership(m_Destination) == 0;
+ }
+ protected bool PhysicalProof()
+ {
+ if (!m_Mine || !m_Source || !m_Donor || m_Source.GetOwner() != m_Donor) return false;
+ if (!ManagedStorage(m_Destination, m_Storage, m_Recipient)
+ || Ownership(m_Source) != 0 || Ownership(m_Destination) != 1) return false;
+ InventoryItemComponent item = InventoryItemComponent.Cast(m_Mine.FindComponent(InventoryItemComponent));
+ return item && item.GetParentSlot() && item.GetParentSlot().GetStorage() == m_Storage && InSlot(item.GetParentSlot());
+ }
+ bool Unresolved()
+ {
+ if (!m_Submitted || !m_Mine) return false;
+ if (m_Acknowledgement == 1 && PhysicalProof()) return false;
+ if (m_Acknowledgement < 0 && OriginalProof()) return false;
+ return true;
+ }
+ protected bool Context()
+ {
+ if (!Replication.IsServer() || SCR_Global.IsEditMode() || !m_Mode || m_Mode != ERR_GameMode.Get() || !m_Mode.Active() || !m_Mode.Forces()) return false;
+ if (!m_Donor || !ERR_Actors.Alive(m_Donor) || !m_DonorGroup || !m_RecipientGroup || m_DonorGroup == m_RecipientGroup) return false;
+ if (!m_Roster || !m_Roster.OwnsRoster(m_RecipientGroup) || m_Strength < 1 || m_Strength > 32) return false;
+ if (m_Roster.InitialStrength() != m_Strength || m_Roster.InitialMember(m_Slot) != m_Recipient
+ || !m_Roster.CanAttribute(m_Slot, m_Recipient, false)) return false;
+ if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_RecipientGroup, m_Recipient) || !ERR_Actors.CanFight(m_Recipient)
+ || CompartmentAccessComponent.GetVehicleIn(m_Recipient)) return false;
+ if (m_DonorGroup.IsSlave() || m_RecipientGroup.IsSlave() || m_DonorGroup.GetPlayerCount(true) > 0) return false;
+ if (!m_Mode.EnemyFaction() || m_DonorGroup.GetFaction() != m_Mode.EnemyFaction()
+ || SCR_FactionManager.SGetFaction(m_Donor) != m_Mode.EnemyFaction()
+ || SCR_FactionManager.SGetFaction(m_Recipient) != m_Mode.EnemyFaction()) return false;
+ if (!m_DonorRecord || !m_RecipientRecord || m_Mode.Forces().FindGroup(m_DonorGroup) != m_DonorRecord
+ || m_Mode.Forces().FindGroup(m_RecipientGroup) != m_RecipientRecord) return false;
+ if (m_DonorRecord.Group != m_DonorGroup || !m_DonorRecord.Physical || m_DonorRecord.Eliminated) return false;
+ if (m_RecipientRecord.Group != m_RecipientGroup || !m_RecipientRecord.Physical || m_RecipientRecord.Eliminated) return false;
+ SCR_AIGroupUtilityComponent utility = m_DonorGroup.GetGroupUtilityComponent();
+ if (!utility || !utility.IsMilitary() || !utility.m_VehicleMgr || !utility.m_VehicleMgr.FindVehicle(m_Donor)) return false;
+ if (ERR_ConvoyRules.Captured(m_Donor, m_DonorGroup)) return false;
+ SCR_AIVehicleUsageComponent usage = SCR_AIVehicleUsageComponent.Cast(m_Donor.FindComponent(SCR_AIVehicleUsageComponent));
+ if (!usage || !usage.GetPilotCompartmentSlot() || !m_Driver || usage.GetPilotCompartmentSlot().GetOccupant() != m_Driver) return false;
+ if (!ERR_EnemyEquipmentRules.Eligible(m_Mode, m_DonorGroup, m_Driver)
+ || SCR_FactionManager.SGetFaction(m_Driver) != m_Mode.EnemyFaction()
+ || !ERR_ConvoyRules.PartOf(CompartmentAccessComponent.GetVehicleIn(m_Driver), m_Donor)) return false;
+ if (!m_Source || !m_Destination || m_Source == m_Destination) return false;
+ if (m_Donor.FindComponent(InventoryStorageManagerComponent) != m_Source
+ || m_Recipient.FindComponent(InventoryStorageManagerComponent) != m_Destination) return false;
+ if (!m_CarriedMine || !ReadyMine(m_Mine)) return false;
+ IEntity exclude;
+ if (m_Submitted) exclude = m_Mine;
+ if (Carrier(m_Recipient, m_RecipientGroup, exclude) != m_CarriedMine) return false;
+ Physics physics = m_Donor.GetPhysics();
+ if (!physics) return false;
+ float speed = physics.GetVelocity().Length(); float angular = physics.GetAngularVelocity().Length();
+ return speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1;
+ }
+ ERR_InfantryAmmoTransferState Tick(float now)
+ {
+ if (m_State != ERR_InfantryAmmoTransferState.PENDING) return m_State;
+ if (!ERR_ForceRules.Finite(now) || now < 0 || now < m_LastTick) return Fail("invalid mine move clock");
+ if (m_Started < 0) m_Started = now;
+ if (now - m_Started >= 8000) return Fail("native mine move deadline expired");
+ m_LastTick = now;
+ if (!Context() || !ERR_InfantryAmmoAccess.Clear(m_Donor, m_Recipient)) return Fail("original mine carrier, truck, stock or physical access lost");
+ if (!m_Submitted)
+ {
+ if (!m_Reservation || !OriginalProof()) return Fail("exact original mine is not exclusive truck stock");
+ m_Storage = m_Destination.FindStorageForItem(m_Mine, EStoragePurpose.PURPOSE_DEPOSIT);
+ if (!ManagedStorage(m_Destination, m_Storage, m_Recipient)
+ || !m_Source.CanMoveItemToStorage(m_Mine, m_Storage, -1)) return Fail("original mine carrier lacks native capacity");
+ m_Callback = new ERR_InfantryMineCallback(); m_Callback.Transfer = this;
+ if (!m_Reservation.Commit() || !m_Reservation.CommittedFor(m_Mine)) return Fail("shared finite mine reservation unavailable");
+ m_Submitted = true;
+ 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");
+ }
+ if (m_Acknowledgement < 0) return Fail("native mine move failed; unproved custody remains quarantined");
+ if (m_Acknowledgement == 1 && PhysicalProof())
+ {
+ m_State = ERR_InfantryAmmoTransferState.SUCCEEDED;
+ m_Reason = "callback and exact carried storage confirm one finite PMN4 transferred";
+ }
+ return m_State;
+ }
+}
--- /dev/null
+++ b/tests/test_infantry_mine_transfer.py
@@ -0,0 +1,137 @@
+"""Actual finite mine transaction/callback and shared reservation; native boundaries doubled."""
+from pathlib import Path
+import os,re
+from policy_harness import ROOT,body,run
+P=Path(os.environ.get('ERR_MINE_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c'))
+s=P.read_text()
+for forbidden in ('GetItem(', 'TrySpawn', 'SetAmmoCount', 'ActivateTrigger(', 'DeleteEntity', 'SetOrigin'):
+ assert forbidden not in s
+assert s.count('TryMoveItemToStorage(')==1
+assert s.index('m_Reservation.Commit()')<s.index('m_Submitted = true;')<s.index('TryMoveItemToStorage(')
+reservation=(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c').read_text()
+stored=body(ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c',' static bool StoredUnder(')
+def cpp(s):
+ s=re.sub(r'//[^\n]*','',s).replace('protected ','').replace('override ','').replace('ref ','').replace('class ','struct ').replace('foreach (','for (').replace('array<','Array<').replace('null','nullptr')
+ s=s.replace('void ERR_InfantryMineTransfer(', 'ERR_InfantryMineTransfer(').replace('void ERR_FiniteMagazineReservation(', 'ERR_FiniteMagazineReservation(')
+ for typ in ('InventoryStorageManagerComponent','InventoryItemComponent','SCR_AIVehicleUsageComponent','SCR_PressureTriggerComponent'):
+ s=re.sub(typ+r'\.Cast\((\w+)\.FindComponent\('+typ+r'\)\)',r'\1.'+{'InventoryStorageManagerComponent':'inventory','InventoryItemComponent':'item','SCR_AIVehicleUsageComponent':'usage','SCR_PressureTriggerComponent':'trigger'}[typ],s)
+ for typ,field in [('InventoryStorageManagerComponent','inventory'),('SCR_AntiPersonnelMineCollisionHandlerComponent','collision'),('SCR_PressureTriggerComponent','trigger'),('DamageManagerComponent','damage')]:
+ s=re.sub(r'(\w+)\.FindComponent\('+typ+r'\)',r'\1.'+field,s)
+ for typ in ('ERR_GameMode','ERR_ForceRecord','ERR_HQStatusSampler','InventoryStorageManagerComponent','InventoryItemComponent','InventoryStorageSlot','BaseInventoryStorageComponent','SCR_AIVehicleUsageComponent','SCR_PressureTriggerComponent','SCR_AIGroupUtilityComponent','Physics','ERR_InfantryMineTransfer','ERR_InfantryMineCallback','ERR_FiniteMagazineReservation'):
+ s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
+ s=s.replace('Array<BaseInventoryStorageComponent>','Array<BaseInventoryStorageComponent*>').replace('Array<IEntity> attempted','Array<IEntity>* attempted')
+ for field in ('m_Original','m_Spent'):
+ s=s.replace('Array<IEntity> '+field,'Array<IEntity>* '+field).replace(field+'.',field+'->')
+ s=s.replace('Array<IEntity> original, Array<IEntity> spent','Array<IEntity>* original, Array<IEntity>* spent')
+ for ns in ('Replication','SCR_Global','ERR_GameMode','ERR_Actors','ERR_EnemyEquipmentRules','ERR_ConvoyRules','SCR_FactionManager','CompartmentAccessComponent','ERR_InfantryAmmoTransfer','ERR_InfantryAmmoAccess','ERR_ForceRules','ERR_EnemyLoadoutCatalog','ERR_InfantryAmmoTransferState','EStoragePurpose'):
+ s=s.replace(ns+'.',ns+'::')
+ for var in ('m_Mine','exactMine','mode','donorVehicle','recipientActor','originalRoster','usage','item','actor','group','inventory','carried','trigger','m_Mode','m_Donor','m_DonorGroup','m_Recipient','m_RecipientGroup','m_Roster','m_DonorRecord','m_RecipientRecord','m_Source','m_Destination','m_Reservation','m_Callback','m_FromSlot','m_FromStorage','m_Storage','slot','storage','utility','physics','Transfer','entity'):
+ s=re.sub(r'\b'+var+r'\.',var+'->',s)
+ for call in ('Forces()','GetPilotCompartmentSlot()','GetPrefabData()','GetParentSlot()'):
+ s=s.replace(call+'.',call+'->')
+ s=s.replace('m_VehicleMgr.','m_VehicleMgr->')
+ s=re.sub(r'\b(bool|int|IEntity) (\w+);',r'\1 \2{};',s)
+ s=re.sub(r'^( \w+\*) (\w+);',r'\1 \2=nullptr;',s,flags=re.M)
+ return s.replace('\n}\n','\n};\n')
+run(r'''
+#include <cassert>
+#include <vector>
+#include <string>
+#include <algorithm>
+#include <cmath>
+#include <functional>
+using string=std::string;
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void RemoveItem(T v){auto i=std::find(this->begin(),this->end(),v);if(i!=this->end())this->erase(i);}};
+struct Entity;struct Group;using IEntity=Entity*;using Vehicle=Entity*;using SCR_AIGroup=Group*;
+struct Vector{float n=0;float Length(){return n;}};
+struct Physics{Vector speed,angular;Vector GetVelocity(){return speed;}Vector GetAngularVelocity(){return angular;}};
+struct BaseInventoryStorageComponent{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
+struct InventoryStorageSlot{IEntity attached=nullptr;BaseInventoryStorageComponent*storage=nullptr;IEntity GetAttachedEntity(){return attached;}BaseInventoryStorageComponent*GetStorage(){return storage;}};
+struct InventoryItemComponent{InventoryStorageSlot*slot=nullptr;InventoryStorageSlot*GetParentSlot(){return slot;}};
+struct SCR_PressureTriggerComponent{bool active=false,initial=false;bool IsActivated(){return active;}bool IsActivatedByDefault(){return initial;}};
+struct Prefab{string name="PMN4";string GetPrefabName(){return name;}};
+struct Seat{IEntity occupant=nullptr;IEntity GetOccupant(){return occupant;}};
+struct SCR_AIVehicleUsageComponent{Seat*seat=nullptr;Seat*GetPilotCompartmentSlot(){return seat;}};
+struct InventoryStorageManagerComponent;
+struct Entity{InventoryStorageManagerComponent*inventory=nullptr;InventoryItemComponent*item=nullptr;SCR_PressureTriggerComponent*trigger=nullptr;SCR_AIVehicleUsageComponent*usage=nullptr;Prefab*prefab=nullptr;Physics*physics=nullptr;IEntity vehicle=nullptr,parent=nullptr;SCR_AIGroup group=nullptr;int faction=1;bool alive=true,fit=true,player=false,collision=true,damage=true;
+ Prefab*GetPrefabData(){return prefab;}Physics*GetPhysics(){return physics;}IEntity GetParent(){return parent;}};
+struct VehicleManager{bool found=true;bool FindVehicle(IEntity){return found;}};
+struct SCR_AIGroupUtilityComponent{bool military=true;VehicleManager*m_VehicleMgr=nullptr;bool IsMilitary(){return military;}};
+struct Group{bool slave=false;int players=0,faction=1;SCR_AIGroupUtilityComponent*utility=nullptr;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}SCR_AIGroupUtilityComponent*GetGroupUtilityComponent(){return utility;}};
+struct ERR_ForceRecord{SCR_AIGroup Group=nullptr;bool Physical=true,Eliminated=false;};
+struct Ledger{SCR_AIGroup donor=nullptr,recipient=nullptr;ERR_ForceRecord*d=nullptr,*r=nullptr;ERR_ForceRecord*FindGroup(SCR_AIGroup g){return g==donor?d:g==recipient?r:nullptr;}};
+struct ERR_GameMode{inline static ERR_GameMode*current=nullptr;bool active=true;Ledger*ledger=nullptr;static ERR_GameMode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}int EnemyFaction(){return 1;}};
+struct ERR_HQStatusSampler{SCR_AIGroup group=nullptr;IEntity actor=nullptr;bool valid=true;int strength=1;bool OwnsRoster(SCR_AIGroup g){return g==group;}int InitialStrength(){return strength;}IEntity InitialMember(int i){return i==0?actor:nullptr;}bool CanAttribute(int i,IEntity a,bool){return valid&&i==0&&a==actor;}};
+struct Replication{inline static bool server=true;static bool IsServer(){return server;}};
+struct SCR_Global{inline static bool edit=false;static bool IsEditMode(){return edit;}};
+struct ERR_Actors{static bool Alive(IEntity e){return e&&e->damage&&e->alive;}static bool CanFight(IEntity e){return Alive(e)&&e->fit;}static bool Player(IEntity e){return e&&e->player;}static SCR_AIGroup Group(IEntity e){return e?e->group:nullptr;}};
+struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode*,SCR_AIGroup g,IEntity e){return g&&!g->slave&&!g->players&&g->faction==1&&ERR_Actors::CanFight(e)&&!e->player&&e->group==g;}};
+struct SCR_FactionManager{static int SGetFaction(IEntity e){return e?e->faction:0;}};
+struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity e){return e?e->vehicle:nullptr;}};
+struct ERR_ConvoyRules{inline static bool captured=false;static bool Captured(IEntity,SCR_AIGroup){return captured;}static bool PartOf(IEntity e,IEntity root){return e&&e==root;}};
+struct ERR_InfantryAmmoAccess{inline static bool clear=true;static bool Clear(IEntity,IEntity){return clear;}};
+struct ERR_ForceRules{static bool Finite(float n){return std::isfinite(n);}};
+struct ERR_EnemyLoadoutCatalog{inline static string MINE="PMN4";};
+struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
+enum ERR_InfantryAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
+struct ScriptedInventoryOperationCallback{virtual ~ScriptedInventoryOperationCallback()=default;virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}};
+struct InventoryStorageManagerComponent{IEntity owner=nullptr;Array<IEntity>items;Array<BaseInventoryStorageComponent*>storages;BaseInventoryStorageComponent*destination=nullptr;bool capacity=true,accept=true;int calls=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
+ IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int=0){a=items;}void GetStorages(Array<BaseInventoryStorageComponent*>&a,int){a=storages;}BaseInventoryStorageComponent*FindStorageForItem(IEntity,int){return destination;}
+ bool CanMoveItemToStorage(IEntity,BaseInventoryStorageComponent*,int){return capacity;}
+ bool TryMoveItemToStorage(IEntity,BaseInventoryStorageComponent*,int,ScriptedInventoryOperationCallback*cb){calls++;last=cb;if(effect)effect();return accept;}};
+struct ERR_InfantryAmmoTransfer{
+'''+cpp(stored)+r'''
+};
+'''+cpp(reservation)+r'''
+struct ERR_InfantryMineTransfer;
+struct ERR_InfantryMineCallback : ScriptedInventoryOperationCallback{ERR_InfantryMineTransfer*Transfer=nullptr;void OnComplete();void OnAllComplete();void OnFailed();void OnAllFailed();};
+'''+cpp(s[s.index('class ERR_InfantryMineTransfer\n'):])+r'''
+'''+''.join(cpp(body(P,'protected override void '+method+'(')).replace('void '+method+'(', 'void ERR_InfantryMineCallback::'+method+'(') for method in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'))+r'''
+struct Fixture{ERR_GameMode mode;Ledger ledger;Group donorGroup,recipientGroup;ERR_ForceRecord donorRecord,recipientRecord;Entity truck,driver,actor,incoming,original,extra;InventoryStorageManagerComponent source,destination;BaseInventoryStorageComponent sourceStorage,destStorage;InventoryStorageSlot from,to,ownSlot,extraSlot;InventoryItemComponent inItem,ownItem,extraItem;Prefab prefab;SCR_PressureTriggerComponent inTrigger,ownTrigger,extraTrigger;SCR_AIVehicleUsageComponent usage;Seat seat;Physics physics;VehicleManager vehicleMgr;SCR_AIGroupUtilityComponent utility;ERR_HQStatusSampler roster;Array<IEntity>stock,spent;ERR_FiniteMagazineReservation reservation;
+ Fixture():reservation(&stock,&spent,&incoming){Replication::server=true;SCR_Global::edit=false;ERR_ConvoyRules::captured=false;ERR_InfantryAmmoAccess::clear=true;ERR_GameMode::current=&mode;mode.ledger=&ledger;
+ ledger.donor=&donorGroup;ledger.recipient=&recipientGroup;ledger.d=&donorRecord;ledger.r=&recipientRecord;donorRecord.Group=&donorGroup;recipientRecord.Group=&recipientGroup;
+ truck.inventory=&source;actor.inventory=&destination;truck.physics=&physics;truck.usage=&usage;usage.seat=&seat;seat.occupant=&driver;driver.vehicle=&truck;driver.group=&donorGroup;actor.group=&recipientGroup;donorGroup.utility=&utility;utility.m_VehicleMgr=&vehicleMgr;
+ source.owner=&truck;destination.owner=&actor;sourceStorage.owner=&truck;destStorage.owner=&actor;source.storages={&sourceStorage};destination.storages={&destStorage};destination.destination=&destStorage;
+ from.storage=&sourceStorage;from.attached=&incoming;to.storage=&destStorage;ownSlot.storage=&destStorage;ownSlot.attached=&original;extraSlot.storage=&destStorage;extraSlot.attached=&extra;
+ incoming.prefab=&prefab;incoming.trigger=&inTrigger;incoming.item=&inItem;inItem.slot=&from;original.prefab=&prefab;original.trigger=&ownTrigger;original.item=&ownItem;ownItem.slot=&ownSlot;extra.prefab=&prefab;extra.trigger=&extraTrigger;extra.item=&extraItem;extraItem.slot=&extraSlot;
+ source.items={&incoming};destination.items={&original};roster.group=&recipientGroup;roster.actor=&actor;stock={&incoming};assert(reservation.Reserve());}
+ ERR_InfantryMineTransfer Job(){return ERR_InfantryMineTransfer(&mode,&truck,&donorGroup,&actor,&recipientGroup,&incoming,&reservation,&roster,0);}
+ void Move(){source.items.clear();destination.items.Insert(&incoming);from.attached=nullptr;to.attached=&incoming;inItem.slot=&to;}
+};
+int main(){
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING&&t.Submitted()&&t.Unresolved()&&f.source.calls==1);assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.ReleaseUnsubmitted());
+ f.source.last->OnComplete();assert(t.Tick(1)==PENDING&&t.Unresolved());f.Move();assert(t.Tick(2)==SUCCEEDED&&!t.Unresolved());assert(f.destination.items.Count()==2);t.Tick(3);assert(f.source.calls==1);}
+ // Partially constructed contexts never bind later or reach native mutation.
+ for(int k=0;k<10;k++){Fixture f;
+ if(k==4)f.mode.ledger=nullptr;if(k==5)f.ledger.d=nullptr;if(k==6)f.ledger.r=nullptr;if(k==7)f.seat.occupant=nullptr;
+ ERR_InfantryMineTransfer t(k==0?nullptr:&f.mode,k==1?nullptr:&f.truck,&f.donorGroup,k==2?nullptr:&f.actor,&f.recipientGroup,k==8?nullptr:&f.incoming,&f.reservation,k==3?nullptr:&f.roster,k==9?1:0);
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
+ // No entitlement for zero/two carriers, uncertain inventory, or replaced original mine.
+ for(int k=0;k<8;k++){Fixture f;if(k==0)f.destination.items.clear();if(k==1)f.destination.items.Insert(&f.extra);if(k==2)f.ownTrigger.active=true;if(k==3)f.ownTrigger.initial=true;if(k==4)f.ownItem.slot=nullptr;if(k==5)f.ownSlot.attached=&f.extra;if(k==6)f.destination.items.Insert(nullptr);if(k==7)f.destination.items.resize(129,&f.original);
+ auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0&&f.reservation.ReleaseUnsubmitted());}
+ {Fixture f;auto t=f.Job();f.destination.items={&f.extra};assert(t.Tick(0)==FAILED&&f.source.calls==0);}
+ {Fixture f;f.extra.damage=false;f.destination.items.Insert(&f.extra);assert(!ERR_InfantryMineTransfer::Carrier(&f.actor,&f.recipientGroup));auto t=f.Job();assert(t.Tick(0)==FAILED&&f.source.calls==0);}
+ // Duplicates in native enumeration are one item, not a second entitlement.
+ {Fixture f;f.destination.items.Insert(&f.original);assert(ERR_InfantryMineTransfer::Carrier(&f.actor,&f.recipientGroup)==&f.original);}
+ // Atomic shared claim before native request, including synchronous callback and false return.
+ {Fixture f;f.source.accept=false;f.source.effect=[&]{assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.ReleaseUnsubmitted());f.Move();f.source.last->OnComplete();};auto t=f.Job();assert(t.Tick(0)==SUCCEEDED&&!t.Unresolved()&&f.source.calls==1);}
+ {Fixture f;ERR_FiniteMagazineReservation rival(&f.stock,&f.spent,&f.incoming);assert(!rival.Reserve());auto t=f.Job();assert(t.Tick(0)==PENDING&&f.spent.Count()==1);}
+ {Fixture f;f.reservation.ReleaseUnsubmitted();auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
+ {Fixture f;f.source.accept=false;auto t=f.Job();assert(t.Tick(0)==FAILED&&t.Submitted()&&t.Unresolved());f.source.last->OnFailed();assert(!t.Unresolved()&&!f.reservation.ReleaseUnsubmitted());t.Tick(1);assert(f.source.calls==1);}
+ // Failure cannot release partial movement; late success is not permission to retry.
+ {Fixture f;auto t=f.Job();t.Tick(0);f.inItem.slot=&f.to;f.source.last->OnFailed();assert(t.Tick(1)==FAILED&&t.Unresolved());f.source.last->OnComplete();assert(t.Unresolved()&&f.source.calls==1);}
+ {Fixture f;auto t=f.Job();t.Tick(0);t.Stop();f.mode.active=false;f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(1)==FAILED&&f.source.calls==1);}
+ {Fixture f;auto t=f.Job();t.Tick(0);assert(t.Tick(8000)==FAILED&&t.Unresolved());f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(8001)==FAILED&&f.source.calls==1);}
+ {Fixture f;auto t=f.Job();t.Tick(10);assert(t.Tick(9)==FAILED&&t.Unresolved()&&f.source.calls==1);}
+ // Only deletion, not damage/capture, settles an unknown one-item movement.
+ {Fixture f;auto t=f.Job();t.Tick(0);t.m_Mine=nullptr;f.reservation.m_Item=nullptr;f.stock[0]=nullptr;f.spent[0]=nullptr;assert(t.Tick(1)==FAILED&&!t.Unresolved());f.source.last->OnComplete();assert(t.Tick(2)==FAILED&&!f.reservation.ReleaseUnsubmitted()&&f.spent.Count()==1);}
+ for(int k=0;k<3;k++){Fixture f;auto t=f.Job();t.Tick(0);if(k==0)f.incoming.alive=false;if(k==1)f.actor.player=true;if(k==2){ERR_ConvoyRules::captured=true;}assert(t.Tick(1)==FAILED&&t.Unresolved()&&f.source.calls==1);}
+ // Original actor/ledger/driver/managers/capacity/reach/stock all remain required.
+ for(int k=0;k<25;k++){Fixture f;auto t=f.Job();ERR_ForceRecord replacement;InventoryStorageManagerComponent changed;
+ switch(k){case 0:Replication::server=false;break;case 1:SCR_Global::edit=true;break;case 2:f.mode.active=false;break;case 3:ERR_GameMode::current=nullptr;break;case 4:f.roster.valid=false;break;case 5:f.roster.actor=&f.extra;break;case 6:f.roster.strength=2;break;case 7:f.actor.fit=false;break;case 8:f.actor.player=true;break;case 9:f.actor.vehicle=&f.truck;break;case 10:f.ledger.r=&replacement;break;case 11:f.donorRecord.Eliminated=true;break;case 12:f.vehicleMgr.found=false;break;case 13:f.seat.occupant=&f.extra;break;case 14:f.truck.inventory=&changed;break;case 15:f.actor.inventory=&changed;break;case 16:f.physics.speed.n=1;break;case 17:f.physics.angular.n=1;break;case 18:ERR_InfantryAmmoAccess::clear=false;break;case 19:f.source.capacity=false;break;case 20:f.destination.destination=nullptr;break;case 21:f.inTrigger.active=true;break;case 22:f.from.attached=&f.extra;break;case 23:f.source.items.clear();break;case 24:f.destination.items.Insert(&f.incoming);break;}
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0);}
+ // Ownership lost after submission remains quarantined until exact physical proof.
+ {Fixture f;auto t=f.Job();t.Tick(0);f.roster.valid=false;assert(t.Tick(1)==FAILED&&t.Unresolved());f.Move();f.source.last->OnComplete();assert(!t.Unresolved()&&t.Tick(2)==FAILED);}
+}
+'''.replace(';if(', ';\n if('))
+print('PASS: actual finite PMN4 one-to-two entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry')
@@ -0,0 +1 @@
PASS: actual finite PMN4 one-to-two entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry
@@ -0,0 +1,19 @@
check: /tmp/err-policy-j3y62ux8/check.cpp:365: int main(): Assertion `!ERR_InfantryMineTransfer::Carrier(&f.actor,&f.recipientGroup)' failed.
Traceback (most recent call last):
File "/tmp/erri-mine-transfer-1724/tests/test_infantry_mine_transfer.py", line 36, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<97 lines>...
}
^
'''.replace(';if(', ';\n if('))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-j3y62ux8/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,19 @@
check: /tmp/err-policy-0nkjkelh/check.cpp:363: int main(): Assertion `t.Tick(0)==FAILED&&!t.Submitted()&&f.source.calls==0&&f.reservation.ReleaseUnsubmitted()' failed.
Traceback (most recent call last):
File "/tmp/erri-mine-transfer-1724/tests/test_infantry_mine_transfer.py", line 36, in <module>
run(r'''
~~~^^^^^
#include <cassert>
^^^^^^^^^^^^^^^^^^
...<97 lines>...
}
^
'''.replace(';if(', ';\n if('))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/jakub/devel/escapistsrereintroduced/tests/policy_harness.py", line 14, in run
subprocess.run([str(p/'check')],check=True)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/lib/python3.14/subprocess.py", line 578, in run
raise CalledProcessError(retcode, process.args,
output=stdout, stderr=stderr)
subprocess.CalledProcessError: Command '['/tmp/err-policy-0nkjkelh/check']' died with <Signals.SIGABRT: 6>.
@@ -0,0 +1,15 @@
{
"test_infantry_mine_transfer.py": 0,
"mutation_remove_single_carrier_guard": {
"exit": 1,
"result": "compiled, actual assertion failure",
"log": "red-two-carriers.log"
},
"mutation_remove_missing_damage_guard": {
"exit": 1,
"result": "compiled, actual assertion failure",
"log": "red-missing-damage.log"
},
"git_apply_check": 0,
"native_run": false
}
@@ -0,0 +1,4 @@
{
"addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryMineTransfer.c": "57977eac877d25c8578116d932b7a3707e465c6d1ac74b7d98b9204e1add861f",
"tests/test_infantry_mine_transfer.py": "6d2013a77b9389e787acff2229629629819a48e752e1425447714fda8bfc8d6b"
}
@@ -0,0 +1 @@
PASS: actual finite PMN4 one-to-two entitlement, pinned original ownership, shared commit, physical custody, callback quarantine/deletion and no retry