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,118 @@
Prepared mounted rearm second courier leg — NOT integrated gameplay
Patch scope
- NEW ERR_MountedAmmoTransfer.c (transaction plus its callback).
- ERR_FiniteMagazineReservation.c: committed flag, exact read-only CommittedFor,
one-shot ClaimMountedSwap; all current Reserve/Commit/Release rules preserved.
- NEW tests/test_mounted_ammo_transfer.py: actual constructor/method/callback
bodies executed with controlled native doubles, not engine coverage.
Do not apply only the shared-helper hunk to production: it is consumed by the
prepared transaction, which itself needs the courier coordinator below.
Constructor contract
ERR_MountedAmmoTransfer(mode, originalTask, targetVehicleIndex,
originalTargetRoster, originalWorkerSlot, originalWeapon, originalMuzzle,
originalLoadedMagazineEntity, alreadyCarriedIncomingEntity, committedPickup)
The coordinator must retain original weapon/muzzle/loaded identities before
courier dispatch, from the exact production mounted collector/ownership checks.
It must retain the original recipient crew sampler, choose a surviving original
member other than the seated original driver, and keep the original vehicle
root bound. Construction pins the exact task/receipt/vehicle objects, roots,
groups, force records, worker, magazine components, storage managers and slots.
It does not discover or bind replacements. Index1 is the original ammo truck;
recipient is another original root in the same six-vehicle task.
Required caller integration (not in this patch)
1. Require a fresh delivered mounted-low-ammo report, then validate original
task/source binding and acquire exclusive recipient/muzzle/worker orders.
2. Select a compatible exact identity from the existing infantry coordinator's
original m_Stock/m_Spent arrays. Reserve via ERR_FiniteMagazineReservation;
keep the same handle alive across both courier legs.
3. Physically move original worker to truck cargo access and perform finite
pickup. Existing ERR_InfantryAmmoTransfer cannot be reused unchanged: its
Compatible(worker,box) demands the worker's carried small arm uses that box.
An AK-equipped crew member carrying a PKM box needs a specific courier pickup
contract, preserving donor storage ownership, physical cargo access, exact
callback + destination proof and the existing no-duplicate quarantine.
4. Commit that handle when pickup is submitted, as for existing infantry.
Start the second leg only after pickup reports SUCCEEDED, with its callback
and exact ownership both proven. A committed handle alone is not proof of a
successful first leg. Keep uncertain pickup claims quarantined.
5. Send the same original worker on foot to the exact original mounted slot;
keep both original vehicles stationary and original drivers/crew valid.
Normal ~35m vehicle spacing does not require moving vehicles together.
Constructor/Tick never move anyone, request cargo or bypass mounted-crew hold.
The coordinator must authorize ordinary dismount/approach/reboarding and
protect these orders from the existing convoy tactics/HQ scheduler.
6. Construct this leaf at the rendezvous; Tick accepts milliseconds and has an
8000ms monotonic deadline. It is not a navigation timer. The original loaded
box must still be <=25% full and incoming must carry more rounds.
7. The caller must hold a strong ref ERR_MountedAmmoTransfer while a native
callback or quarantine is live; the callback Transfer backlink is weak.
Keep both exact items/slots and the recipient claim reserved while Unresolved
is true, including after Stop, failure, deadline or mission end. Polling proof
after late callback can settle quarantine, but never restarts or succeeds a
stopped/failed transaction. A false native return without callback stays
conservatively unresolved. Never refund the incoming finite identity.
8. Success leaves the original outgoing box carried by the worker. It is not
automatically new usable stock, nor is it returned remotely to the truck.
A later physical return/reboarding/cleanup stage is still needed.
Leaf behavior
- Context rechecks current server/active mode, exact consumed receipt/record
identity, six original task slots, original root/crew bindings, worker roster,
on-foot conscious enemy ownership, alive stationary truck and recipient.
- Source is the original worker's managed deposit storage, <=64 storages and
<=128 inventory identities. Incoming and outgoing must both be absent from
the original truck inventory. No same-item swap, remote truck swap or reload
fallback. Original gun/mount/muzzle relation and exact parent slots retained.
- Reach is <=4m from worker aiming position to the exact authored mounted
InventoryStorageSlot.GetWorldTransform position, with finite coordinates and
WORLD|ENTS camera-layer trace. Only worker and actual gun hierarchies are
excluded; original vehicle hull and unrelated objects remain occluders.
- Only native mutation is one TrySwapItemStorages(incoming,outgoing,callback)
through the original worker manager, preceded by CanSwapItemStorages. The
committed finite reservation grants at most one mounted native submission
across competing transaction objects, consumed before calling native code.
- No content mutation, chamber manipulation, spawn, delete, replacement,
teleport, retry or reservation refund. Native swap handles existing contents.
- Callback alone is insufficient. Success requires BOTH exact parent/attached
slots exchanged, actual muzzle magazine switched, and worker inventory
contains outgoing but not incoming. Callback failure remains sticky; partial
movement stays quarantined. Inline success + native false is accepted only
with ordinary proof; no late callback changes terminal gameplay state.
Established native APIs and remaining evidence
- generated/InventorySystem/InventoryStorageManagerComponent.c lines35-37:
CanSwapItemStorages(itemA,itemB), TrySwapItemStorages(itemA,itemB,cb).
- generated/InventorySystem/InventoryTask/ScriptedInventoryOperationCallback.c:
OnComplete/OnFailed for an operation; OnAllComplete/OnAllFailed described for
multiple spawns. Both success notifications may be accepted here because
exact two-item slot proof is mandatory; no assumption of per-item ordering.
- generated/InventorySystem/InventoryStorageSlot.c -> EntitySlotInfo ->
generated/PointInfo.c: GetWorldTransform(out vector[4]). All above installed
references under /tmp/erri-vanilla-stock/scripts/Game.
- /tmp/erri-horn-read/MG_PKM_base.et authors magazine pivot/context around
lines289-294. Stock support-station action scripts use their support/resupply
framework; this leaf never invokes or simulates those resource-based actions.
- Prior native dry query proved original PKM slot/storage and compatible initial
truck box can be swapped, without mutation. It did NOT prove worker-carried
CanSwap, authored slot reach, collision result, actual native swap, callback
sequence, worker carrying capacity, outgoing return or multiplayer behavior.
Those require an explicit bounded native courier fixture after integration.
Focused validation (all PASS with proposed shared helper)
PYTHONPATH=/home/jakub/devel/escapistsrereintroduced/tests \
ERR_MOUNTED_TRANSFER_SOURCE=/tmp/erri-mounted-transfer/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c \
ERR_FINITE_RESERVATION_SOURCE=/tmp/erri-mounted-transfer/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c \
python3 /tmp/erri-mounted-transfer/tests/test_mounted_ammo_transfer.py
ERR_FINITE_RESERVATION_SOURCE=/tmp/erri-mounted-transfer/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c \
python3 /home/jakub/devel/escapistsrereintroduced/tests/test_finite_magazine_reservation.py
ERR_FINITE_RESERVATION_SOURCE=/tmp/erri-mounted-transfer/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c \
python3 /home/jakub/devel/escapistsrereintroduced/tests/test_infantry_ammo_transfer.py
No target source writes, native sessions, staging or full-suite run by this task.
Independent read-only review: hq_memory_review found no concrete blocker and
independently executed the actual transfer regression successfully.
@@ -0,0 +1,456 @@
diff --git a/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c b/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c
@@ -7,6 +7,8 @@
protected IEntity m_Item;
protected bool m_Owned;
protected bool m_Closed;
+ protected bool m_Committed;
+ protected bool m_MountedClaimed;
void ERR_FiniteMagazineReservation(array<IEntity> original, array<IEntity> spent, IEntity item)
{
m_Original = original; m_Spent = spent; m_Item = item;
@@ -25,7 +27,20 @@
bool Commit()
{
if (!m_Owned || m_Closed) return false;
- m_Closed = true;
+ m_Closed = true; m_Committed = true;
+ return true;
+ }
+ // A courier continuation consumes the same committed pickup claim, not a new
+ // stock reservation. A released/stale handle can never authorize this leg.
+ bool CommittedFor(IEntity exact)
+ {
+ return exact && exact == m_Item && m_Owned && m_Committed && m_Original && m_Spent
+ && m_Original.Count() <= 96 && m_Spent.Count() <= 96 && m_Original.Contains(exact) && m_Spent.Contains(exact);
+ }
+ bool ClaimMountedSwap(IEntity exact)
+ {
+ if (m_MountedClaimed || !CommittedFor(exact)) return false;
+ m_MountedClaimed = true;
return true;
}
// Caller may release only when no native operation was submitted. Closing
diff --git a/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
new file mode 100644
--- /dev/null
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c
@@ -0,0 +1,238 @@
+enum ERR_MountedAmmoTransferState { FAILED = -1, PENDING, SUCCEEDED }
+
+class ERR_MountedAmmoCallback : ScriptedInventoryOperationCallback
+{
+ ERR_MountedAmmoTransfer 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); }
+}
+
+// Second courier leg only. The caller retains the original gun/muzzle/loaded
+// identities before dispatch, the original worker roster, and the committed
+// finite pickup reservation. Fresh delivered demand and exclusive HQ orders
+// remain caller responsibilities. This class never picks up from a remote truck.
+class ERR_MountedAmmoTransfer
+{
+ protected ERR_GameMode m_Mode;
+ protected ERR_ConvoyTask m_Task;
+ protected ERR_ConvoyReceipt m_Receipt;
+ protected ERR_ConvoyVehicle m_Donor;
+ protected ERR_ConvoyVehicle m_Target;
+ protected Vehicle m_Truck;
+ protected Vehicle m_Root;
+ protected SCR_AIGroup m_DonorGroup;
+ protected SCR_AIGroup m_TargetGroup;
+ protected ERR_ForceRecord m_DonorRecord;
+ protected ERR_ForceRecord m_TargetRecord;
+ protected ERR_HQStatusSampler m_Roster;
+ protected int m_Index;
+ protected int m_WorkerSlot;
+ protected IEntity m_Worker;
+ protected BaseWeaponComponent m_Weapon;
+ protected WeaponSlotComponent m_Mount;
+ protected InventoryStorageSlot m_WeaponSlot;
+ protected BaseMuzzleComponent m_Muzzle;
+ protected IEntity m_Loaded;
+ protected IEntity m_Incoming;
+ protected BaseMagazineComponent m_OldMagazine;
+ protected BaseMagazineComponent m_NewMagazine;
+ protected InventoryStorageSlot m_FromSlot;
+ protected InventoryStorageSlot m_ToSlot;
+ protected BaseInventoryStorageComponent m_FromStorage;
+ protected BaseInventoryStorageComponent m_ToStorage;
+ protected InventoryStorageManagerComponent m_Carried;
+ protected InventoryStorageManagerComponent m_TruckInventory;
+ protected ref ERR_FiniteMagazineReservation m_Reservation;
+ protected ref ERR_MountedAmmoCallback m_Callback;
+ protected bool m_Submitted;
+ protected int m_Acknowledgement;
+ protected float m_Started = -1;
+ protected float m_LastTick = -1;
+ protected ERR_MountedAmmoTransferState m_State = ERR_MountedAmmoTransferState.PENDING;
+ protected string m_Reason;
+ void ERR_MountedAmmoTransfer(ERR_GameMode mode, ERR_ConvoyTask task, int vehicleIndex, ERR_HQStatusSampler originalRoster, int workerSlot, BaseWeaponComponent originalWeapon, BaseMuzzleComponent originalMuzzle, IEntity originalLoaded, IEntity carriedIncoming, ERR_FiniteMagazineReservation committedPickup)
+ {
+ m_Mode = mode; m_Task = task; m_Index = vehicleIndex; m_Roster = originalRoster; m_WorkerSlot = workerSlot;
+ m_Weapon = originalWeapon; m_Muzzle = originalMuzzle; m_Loaded = originalLoaded; m_Incoming = carriedIncoming; m_Reservation = committedPickup;
+ if (!mode || !mode.Forces() || !task || task.Vehicles.Count() != 6 || vehicleIndex < 0 || vehicleIndex >= 6 || vehicleIndex == 1 || !originalRoster) return;
+ m_Receipt = task.Receipt; m_Donor = task.Vehicles[1]; m_Target = task.Vehicles[vehicleIndex];
+ if (!m_Donor || !m_Target) return;
+ m_Truck = m_Donor.Entity; m_Root = m_Target.Entity; m_DonorGroup = m_Donor.Group; m_TargetGroup = m_Target.Group;
+ if (!m_Truck || !m_Root || !m_DonorGroup || !m_TargetGroup) return;
+ m_DonorRecord = mode.Forces().FindGroup(m_DonorGroup); m_TargetRecord = mode.Forces().FindGroup(m_TargetGroup);
+ m_Worker = originalRoster.InitialMember(workerSlot);
+ if (!m_Worker || !m_Weapon || !m_Weapon.GetOwner() || !m_Loaded || !m_Incoming || m_Loaded == m_Incoming) return;
+ InventoryItemComponent gunItem = InventoryItemComponent.Cast(m_Weapon.GetOwner().FindComponent(InventoryItemComponent));
+ InventoryItemComponent loadedItem = InventoryItemComponent.Cast(m_Loaded.FindComponent(InventoryItemComponent));
+ InventoryItemComponent incomingItem = InventoryItemComponent.Cast(m_Incoming.FindComponent(InventoryItemComponent));
+ if (!gunItem || !loadedItem || !incomingItem) return;
+ m_WeaponSlot = gunItem.GetParentSlot(); m_ToSlot = loadedItem.GetParentSlot(); m_FromSlot = incomingItem.GetParentSlot();
+ if (!m_WeaponSlot || !m_ToSlot || !m_FromSlot) return;
+ m_Mount = WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer());
+ m_FromStorage = m_FromSlot.GetStorage(); m_ToStorage = m_ToSlot.GetStorage();
+ m_OldMagazine = BaseMagazineComponent.Cast(m_Loaded.FindComponent(BaseMagazineComponent));
+ m_NewMagazine = BaseMagazineComponent.Cast(m_Incoming.FindComponent(BaseMagazineComponent));
+ m_Carried = InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent));
+ m_TruckInventory = InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent));
+ }
+ bool Submitted() { return m_Submitted; }
+ string Reason() { return m_Reason; }
+ // A failure remains sticky; success notification alone proves neither item.
+ void Acknowledge(bool success)
+ {
+ if (!m_Submitted) return;
+ if (!success) m_Acknowledgement = -1;
+ else if (m_Acknowledgement == 0) m_Acknowledgement = 1;
+ }
+ protected ERR_MountedAmmoTransferState Fail(string reason)
+ {
+ if (m_State == ERR_MountedAmmoTransferState.PENDING) { m_State = ERR_MountedAmmoTransferState.FAILED; m_Reason = reason; }
+ return m_State;
+ }
+ void Stop() { Fail("courier authority stopped; submitted swaps cannot be cancelled or refunded"); }
+ protected static bool Stationary(Vehicle vehicle)
+ {
+ if (!vehicle || !ERR_Actors.Alive(vehicle) || !vehicle.GetPhysics()) return false;
+ float speed = vehicle.GetPhysics().GetVelocity().Length();
+ float angular = vehicle.GetPhysics().GetAngularVelocity().Length();
+ return speed >= 0 && speed <= 0.25 && angular >= 0 && angular <= 0.1;
+ }
+ 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_Task || !m_Task.Ready || m_Task.Stopped || !m_Receipt || m_Task.Receipt != m_Receipt || !m_Receipt.Consumed
+ || m_Task.Vehicles.Count() != 6 || m_Receipt.Records.Count() < 6 || m_Index < 0 || m_Index >= 6 || m_Index == 1) return false;
+ if (!m_Donor || !m_Target || m_Task.Vehicles[1] != m_Donor || m_Task.Vehicles[m_Index] != m_Target
+ || m_Donor.Index != 1 || m_Target.Index != m_Index || m_Donor.Entity != m_Truck || m_Target.Entity != m_Root
+ || m_Donor.Group != m_DonorGroup || m_Target.Group != m_TargetGroup || m_Donor.Released || m_Target.Released
+ || !m_Donor.Registered || !m_Target.Registered || !m_Donor.CrewValid(m_Mode) || !m_Target.CrewValid(m_Mode)) return false;
+ if (!m_DonorRecord || !m_TargetRecord || m_Mode.Forces().FindGroup(m_DonorGroup) != m_DonorRecord
+ || m_Mode.Forces().FindGroup(m_TargetGroup) != m_TargetRecord || m_DonorRecord.Id != m_Receipt.Records[1]
+ || m_TargetRecord.Id != m_Receipt.Records[m_Index]) return false;
+ ERR_HQ hq = ERR_HQ.Cast(m_Mode.Command());
+ if (!hq || !hq.ValidateBoundVehicle(m_DonorGroup, m_Truck) || !hq.ValidateBoundVehicle(m_TargetGroup, m_Root)) return false;
+ if (!m_Roster || !m_Roster.OwnsRoster(m_TargetGroup) || !m_Roster.ValidateBoundVehicle(m_Root)
+ || !m_Roster.CanAttribute(m_WorkerSlot, m_Worker, false) || !m_Target.Members.Contains(m_Worker)
+ || !ERR_EnemyEquipmentRules.Eligible(m_Mode, m_TargetGroup, m_Worker) || !ERR_Actors.CanFight(m_Worker)
+ || CompartmentAccessComponent.GetVehicleIn(m_Worker)) return false;
+ if (!m_Reservation || !m_Reservation.CommittedFor(m_Incoming) || !Stationary(m_Truck) || !Stationary(m_Root)) return false;
+ if (InventoryStorageManagerComponent.Cast(m_Worker.FindComponent(InventoryStorageManagerComponent)) != m_Carried
+ || InventoryStorageManagerComponent.Cast(m_Truck.FindComponent(InventoryStorageManagerComponent)) != m_TruckInventory) return false;
+ return TargetShape();
+ }
+ protected bool TargetShape()
+ {
+ if (!m_Root || !m_Weapon || !m_Weapon.GetOwner() || !m_Mount || !m_WeaponSlot || !m_Muzzle || !m_ToSlot || !m_ToStorage) return false;
+ IEntity gun = m_Weapon.GetOwner();
+ InventoryItemComponent item = InventoryItemComponent.Cast(gun.FindComponent(InventoryItemComponent));
+ if (!item || item.GetParentSlot() != m_WeaponSlot || m_Mount.GetSlotInfo() != m_WeaponSlot || m_Mount.GetWeaponEntity() != gun
+ || m_WeaponSlot.GetAttachedEntity() != gun || m_WeaponSlot.GetParentContainer() != m_Mount
+ || !ERR_ConvoyRules.PartOf(m_Mount.GetOwner(), m_Root) || !ERR_ConvoyRules.PartOf(m_Muzzle.GetOwner(), m_Root)
+ || m_ToSlot.GetParentContainer() != m_Muzzle || m_ToSlot.GetStorage() != m_ToStorage || m_ToStorage.GetOwner() != gun
+ || m_Muzzle.GetMuzzleType() != EMuzzleType.MT_BaseMuzzle) return false;
+ array<BaseMuzzleComponent> muzzles = {}; m_Weapon.GetMuzzlesList(muzzles);
+ return muzzles.Count() <= 8 && muzzles.Contains(m_Muzzle);
+ }
+ protected bool MagazineValid(BaseMagazineComponent magazine)
+ {
+ if (!magazine || !m_Muzzle || !magazine.GetMagazineWell() || !m_Muzzle.GetMagazineWell()
+ || !magazine.GetMagazineWell().IsInherited(m_Muzzle.GetMagazineWell().Type())) return false;
+ int capacity = magazine.GetMaxAmmoCount(); int rounds = magazine.GetAmmoCount();
+ return capacity > 0 && capacity <= 100000 && rounds >= 0 && rounds <= capacity;
+ }
+ protected bool CarriedStorage()
+ {
+ if (!m_Carried || !m_Worker || m_Carried.GetOwner() != m_Worker || !m_FromStorage
+ || !m_FromSlot || m_FromSlot.GetStorage() != m_FromStorage || !ERR_InfantryAmmoTransfer.StoredUnder(m_FromStorage.GetOwner(), m_Worker)) return false;
+ array<BaseInventoryStorageComponent> storages = {}; m_Carried.GetStorages(storages, EStoragePurpose.PURPOSE_DEPOSIT);
+ return storages.Count() <= 64 && storages.Contains(m_FromStorage);
+ }
+ protected int Owned(InventoryStorageManagerComponent inventory, IEntity item)
+ {
+ if (!inventory || !item) return -1;
+ array<IEntity> items = {}; inventory.GetItems(items, EStoragePurpose.PURPOSE_DEPOSIT);
+ if (items.Count() > 128) return -1;
+ if (items.Contains(item)) return 1;
+ return 0;
+ }
+ protected bool InSlot(IEntity entity, InventoryStorageSlot slot)
+ {
+ if (!entity || !slot || slot.GetAttachedEntity() != entity) return false;
+ InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));
+ return item && item.GetParentSlot() == slot;
+ }
+ // Exact two-way proof remains observable after Stop, loss or mission end.
+ protected bool OriginalProof()
+ {
+ return TargetShape() && CarriedStorage() && m_Muzzle.GetMagazine() == m_OldMagazine
+ && InSlot(m_Incoming, m_FromSlot) && InSlot(m_Loaded, m_ToSlot)
+ && Owned(m_Carried, m_Incoming) == 1 && Owned(m_Carried, m_Loaded) == 0;
+ }
+ protected bool SwappedProof()
+ {
+ return TargetShape() && CarriedStorage() && m_Muzzle.GetMagazine() == m_NewMagazine
+ && InSlot(m_Incoming, m_ToSlot) && InSlot(m_Loaded, m_FromSlot)
+ && Owned(m_Carried, m_Incoming) == 0 && Owned(m_Carried, m_Loaded) == 1;
+ }
+ bool Unresolved()
+ {
+ if (!m_Submitted) 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
+ // swap of these slots. Keep both identities and the target claim quarantined.
+ return true;
+ }
+ protected bool AccessFilter(notnull IEntity entity)
+ {
+ return !ERR_ConvoyRules.PartOf(entity, m_Worker) && !ERR_ConvoyRules.PartOf(entity, m_Weapon.GetOwner());
+ }
+ protected bool Reach()
+ {
+ ChimeraCharacter character = ChimeraCharacter.Cast(m_Worker);
+ if (!character || !m_ToSlot || !m_Weapon || !GetGame() || !GetGame().GetWorld()) return false;
+ vector matrix[4]; m_ToSlot.GetWorldTransform(matrix);
+ vector start = character.AimingPosition(); vector point = matrix[3];
+ if (!ERR_ForceRules.FiniteVector(start) || !ERR_ForceRules.FiniteVector(point)) return false;
+ float distance = vector.Distance(start, point);
+ if (!(distance >= 0.05 && distance <= 4)) return false;
+ TraceParam trace = new TraceParam(); trace.Start = start; trace.End = point;
+ trace.Flags = TraceFlags.WORLD | TraceFlags.ENTS; trace.LayerMask = TRACE_LAYER_CAMERA;
+ // The original vehicle hull remains an occluder. This is reach to the exact
+ // authored magazine slot, not a support-station action or a vehicle-center test.
+ return GetGame().GetWorld().TraceMove(trace, AccessFilter) == 1;
+ }
+ ERR_MountedAmmoTransferState Tick(float now)
+ {
+ if (m_State != ERR_MountedAmmoTransferState.PENDING) return m_State;
+ if (!ERR_ForceRules.Finite(now) || now < 0) return Fail("invalid swap clock");
+ if (m_Started < 0) m_Started = now;
+ if (now < m_LastTick || now - m_Started >= 8000) return Fail("mounted swap deadline expired");
+ m_LastTick = now;
+ if (!Context() || !Reach()) return Fail("original convoy, worker, bound mount or unobstructed physical reach lost");
+ if (!m_Submitted)
+ {
+ if (!OriginalProof() || !m_OldMagazine || !m_NewMagazine || m_OldMagazine.GetOwner() != m_Loaded || m_NewMagazine.GetOwner() != m_Incoming
+ || !MagazineValid(m_OldMagazine) || !MagazineValid(m_NewMagazine) || m_NewMagazine.IsUsed()
+ || m_NewMagazine.GetAmmoCount() <= m_OldMagazine.GetAmmoCount() || m_OldMagazine.GetAmmoCount() > m_OldMagazine.GetMaxAmmoCount() * 0.25
+ || m_FromSlot == m_ToSlot || m_FromSlot.IsLocked() || m_ToSlot.IsLocked()
+ || !m_TruckInventory || m_TruckInventory.GetOwner() != m_Truck || Owned(m_TruckInventory, m_Incoming) != 0 || Owned(m_TruckInventory, m_Loaded) != 0)
+ return Fail("exact carried finite stock or original low mounted magazine unavailable");
+ if (!m_Carried.CanSwapItemStorages(m_Incoming, m_Loaded)) return Fail("native slot swap preflight refused");
+ if (!m_Reservation.ClaimMountedSwap(m_Incoming)) return Fail("committed pickup already claimed by another mounted swap");
+ m_Callback = new ERR_MountedAmmoCallback(); m_Callback.Transfer = this;
+ 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");
+ }
+ if (m_Acknowledgement < 0) return Fail("native swap failure; partial or unproved movement remains quarantined");
+ if (m_Acknowledgement == 1 && SwappedProof())
+ {
+ m_State = ERR_MountedAmmoTransferState.SUCCEEDED;
+ m_Reason = "callback plus exact mounted and carried slots confirm one two-way physical magazine exchange";
+ }
+ return m_State;
+ }
+}
diff --git a/tests/test_mounted_ammo_transfer.py b/tests/test_mounted_ammo_transfer.py
new file mode 100644
--- /dev/null
+++ b/tests/test_mounted_ammo_transfer.py
@@ -0,0 +1,174 @@
+"""Execute the proposed exact carried-to-mounted swap; no engine coverage claim."""
+import os,re
+from pathlib import Path
+from policy_harness import ROOT,run
+from test_finite_magazine_reservation import translated_reservation
+p=Path(os.environ.get('ERR_MOUNTED_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c'))
+s=p.read_text()
+for forbidden in ('TrySpawn','SetAmmoCount','SetOrigin','SetTransform','DeleteEntity','TryMove','TryInsert','TryRemove','PerformAction'):
+ assert forbidden not in s,forbidden
+assert s.count('TrySwapItemStorages(')==1
+code=s[s.index('class ERR_MountedAmmoTransfer\n'):]
+code=re.sub(r'//[^\n]*','',code).replace('protected ','').replace('notnull ','').replace('ref ','').replace('class ','struct ')
+code=code.replace('void ERR_MountedAmmoTransfer(','ERR_MountedAmmoTransfer(')
+code=code.replace('array<','Array<').replace('null','nullptr')
+# Only engine component lookup/casts are doubled; all transaction methods execute.
+cast_types={'InventoryItemComponent':'item','BaseMagazineComponent':'magazine','InventoryStorageManagerComponent':'inventory'}
+for kind,field in cast_types.items():
+ code=re.sub(kind+r'\.Cast\(([^;\n]*?)\.FindComponent\('+kind+r'\)\)',lambda m:m[1]+'.'+field,code)
+code=code.replace('WeaponSlotComponent.Cast(m_WeaponSlot.GetParentContainer())','dynamic_cast<Mount*>(m_WeaponSlot.GetParentContainer())')
+code=code.replace('ERR_HQ.Cast(m_Mode.Command())','m_Mode.Command()').replace('ChimeraCharacter.Cast(m_Worker)','m_Worker')
+for name in ('ERR_GameMode','ERR_MountedAmmoTransferState','ERR_ForceRules','ERR_Actors','ERR_ConvoyRules','ERR_EnemyEquipmentRules','ERR_InfantryAmmoTransfer','Replication','SCR_Global','CompartmentAccessComponent','EMuzzleType','EStoragePurpose','vector','TraceFlags'):
+ code=code.replace(name+'.',name+'::')
+for name in ('m_Mode','mode','task','originalRoster','m_Task','m_Receipt','m_Donor','m_Target','m_Truck','m_Root','m_DonorRecord','m_TargetRecord','m_Roster','m_Weapon','m_WeaponSlot','m_Muzzle','m_Loaded','m_Incoming','m_Mount','m_FromStorage','m_ToStorage','m_OldMagazine','m_NewMagazine','m_Carried','m_TruckInventory','m_Reservation','m_Callback','m_Worker','gunItem','loadedItem','incomingItem','gun','item','hq','vehicle','entity','slot','inventory','magazine','character','trace','m_FromSlot','m_ToSlot'):
+ code=re.sub(r'\b'+name+r'\.',name+'->',code)
+code=code.replace(').',' )->')
+# Physics/vector return values, unlike components, are not pointers.
+code=code.replace('GetVelocity( )->Length()', 'GetVelocity().Length()').replace('GetAngularVelocity( )->Length()', 'GetAngularVelocity().Length()')
+code=code.replace('ERR_FiniteMagazineReservation m_Reservation','ERR_FiniteMagazineReservation* m_Reservation').replace('ERR_FiniteMagazineReservation committedPickup','ERR_FiniteMagazineReservation* committedPickup')
+code=code.replace('ERR_MountedAmmoCallback m_Callback','ERR_MountedAmmoCallback* m_Callback')
+code=code.replace('TraceParam trace = new TraceParam()', 'TraceParam* trace = new TraceParam()')
+code=code.replace('TraceMove(trace, AccessFilter)', 'TraceMove(trace, [this](IEntity e){return AccessFilter(e);})')
+code=re.sub(r'\b(bool|int|IEntity|Vehicle|ERR_GameMode|ERR_ConvoyTask|ERR_ConvoyReceipt|ERR_ConvoyVehicle|SCR_AIGroup|ERR_ForceRecord|ERR_HQStatusSampler|BaseWeaponComponent|WeaponSlotComponent|InventoryStorageSlot|BaseMuzzleComponent|BaseMagazineComponent|BaseInventoryStorageComponent|InventoryStorageManagerComponent|ERR_FiniteMagazineReservation\*|ERR_MountedAmmoCallback\*) (\w+);',r'\1 \2{};',code)
+code=code.replace('\n}\n','\n};\n')
+callbacks=[]
+for name in ('OnComplete','OnAllComplete','OnFailed','OnAllFailed'):
+ m=re.search('protected override void '+name+r'\(\) (\{[^\n]+\})',s)
+ callbacks.append('void ERR_MountedAmmoCallback::'+name+'() '+m[1].replace('Transfer.','Transfer->'))
+pre=r'''
+#include <cassert>
+#include <cmath>
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <functional>
+#include <limits>
+using string=std::string;
+template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool Contains(T x){return std::find(this->begin(),this->end(),x)!=this->end();}void Insert(T x){this->push_back(x);}void RemoveItem(T x){this->erase(std::remove(this->begin(),this->end(),x),this->end());}};
+struct vector{float x=0,y=0,z=0;float Length(){return std::sqrt(x*x+y*y+z*z);}static float Distance(vector a,vector b){return vector{a.x-b.x,a.y-b.y,a.z-b.z}.Length();}};
+struct Entity;struct Group;struct Mode;struct Task;struct Receipt;struct ConvoyVehicle;struct Record;struct Roster;struct Weapon;struct Mount;struct Slot;struct Muzzle;struct Magazine;struct Storage;struct Inventory;struct HQ;
+using IEntity=Entity*;using Vehicle=Entity*;using ChimeraCharacter=Entity*;using SCR_AIGroup=Group*;using ERR_GameMode=Mode*;using ERR_ConvoyTask=Task*;using ERR_ConvoyReceipt=Receipt*;using ERR_ConvoyVehicle=ConvoyVehicle*;using ERR_ForceRecord=Record*;using ERR_HQStatusSampler=Roster*;using BaseWeaponComponent=Weapon*;using WeaponSlotComponent=Mount*;using InventoryStorageSlot=Slot*;using BaseMuzzleComponent=Muzzle*;using BaseMagazineComponent=Magazine*;using BaseInventoryStorageComponent=Storage*;using InventoryStorageManagerComponent=Inventory*;using ERR_HQ=HQ*;
+struct Component{virtual ~Component()=default;};
+struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Item*;
+struct Physics{vector velocity,angular;vector GetVelocity(){return velocity;}vector GetAngularVelocity(){return angular;}};
+struct Entity{Entity*parent=nullptr;bool alive=true,eligible=true,awake=true;IEntity seated=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Inventory*inventory=nullptr;Physics*physics=nullptr;vector position;Physics*GetPhysics(){return physics;}vector AimingPosition(){return position;}};
+struct Slot{Storage*storage=nullptr;Component*container=nullptr;IEntity attached=nullptr;bool locked=false;vector point{1,0,0};Storage*GetStorage(){return storage;}Component*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}bool IsLocked(){return locked;}void GetWorldTransform(vector mat[4]){mat[3]=point;}};
+struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
+struct Well{int type=1;int Type(){return type;}bool IsInherited(int t){return t==type;}};
+struct Magazine{IEntity owner=nullptr;Well*well=nullptr;int rounds=100,capacity=100;bool used=false;IEntity GetOwner(){return owner;}Well*GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}bool IsUsed(){return used;}};
+struct Muzzle:Component{IEntity owner=nullptr;Magazine*mag=nullptr;Well*well=nullptr;int kind=0;IEntity GetOwner(){return owner;}Magazine*GetMagazine(){return mag;}Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
+struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;IEntity GetOwner(){return owner;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
+struct Mount:Component{IEntity owner=nullptr;IEntity gun=nullptr;Slot*slot=nullptr;IEntity GetOwner(){return owner;}IEntity GetWeaponEntity(){return gun;}Slot*GetSlotInfo(){return slot;}};
+struct Group{};
+struct Record{string Id;};
+struct Ledger{SCR_AIGroup donor=nullptr,target=nullptr;Record*d=nullptr,*t=nullptr;Record*FindGroup(SCR_AIGroup g){return g==donor?d:g==target?t:nullptr;}};
+struct HQ{bool valid=true;bool ValidateBoundVehicle(SCR_AIGroup g,IEntity e){return valid&&g&&e;}};
+struct Mode{inline static Mode*current=nullptr;bool active=true;Ledger*ledger=nullptr;HQ*hq=nullptr;static Mode*Get(){return current;}bool Active(){return active;}Ledger*Forces(){return ledger;}HQ*Command(){return hq;}};
+struct Receipt{bool Consumed=true;Array<string>Records;};
+struct ConvoyVehicle{int Index=0;IEntity Entity=nullptr;SCR_AIGroup Group=nullptr;bool Released=false,Registered=true,crew=true;Array<IEntity>Members;bool CrewValid(ERR_GameMode){return crew;}};
+struct Task{bool Ready=true,Stopped=false;ERR_ConvoyReceipt Receipt=nullptr;Array<ERR_ConvoyVehicle>Vehicles;};
+struct Roster{SCR_AIGroup group=nullptr;IEntity root=nullptr,worker=nullptr;bool valid=true;IEntity InitialMember(int slot){return slot==1?worker:nullptr;}bool OwnsRoster(SCR_AIGroup g){return valid&&g==group;}bool ValidateBoundVehicle(IEntity e){return valid&&e==root;}bool CanAttribute(int slot,IEntity e,bool observe){assert(!observe);return valid&&slot==1&&e==worker;}};
+struct Replication{inline static bool server=true;static bool IsServer(){return server;}};struct SCR_Global{inline static bool editor=false;static bool IsEditMode(){return editor;}};
+struct ERR_Actors{static bool Alive(IEntity e){return e&&e->alive;}static bool CanFight(IEntity e){return e&&e->alive&&e->awake;}};
+struct ERR_ConvoyRules{static bool PartOf(IEntity e,IEntity root){for(int i=0;e&&i<32;i++,e=e->parent)if(e==root)return true;return false;}};
+struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){return ERR_ConvoyRules::PartOf(e,root);}};
+struct ERR_EnemyEquipmentRules{static bool Eligible(ERR_GameMode m,SCR_AIGroup g,IEntity e){return m&&g&&e&&e->eligible;}};
+struct CompartmentAccessComponent{static IEntity GetVehicleIn(IEntity e){return e?e->seated:nullptr;}};
+struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}static bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}};
+struct EMuzzleType{static const int MT_BaseMuzzle=0;};struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
+struct TraceParam{vector Start,End;int Flags=0,LayerMask=0;};struct TraceFlags{static const int WORLD=1,ENTS=2;};const int TRACE_LAYER_CAMERA=4;
+struct World{float fraction=1;Array<IEntity>blockers;int calls=0;float TraceMove(TraceParam*t,std::function<bool(IEntity)>filter){assert(t->Flags==3&&t->LayerMask==4);calls++;for(auto*e:blockers)if(filter(e))return 0;return fraction;}};
+struct Game{World*world=nullptr;World*GetWorld(){return world;}};Game*game=nullptr;Game*GetGame(){return game;}
+struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
+struct ERR_MountedAmmoTransfer;
+struct ERR_MountedAmmoCallback:ScriptedInventoryOperationCallback{ERR_MountedAmmoTransfer*Transfer=nullptr;void OnComplete()override;void OnAllComplete()override;void OnFailed()override;void OnAllFailed()override;};
+struct Inventory{IEntity owner=nullptr;Array<IEntity>items;Array<Storage*>storages;bool allow=true,accept=true;int queries=0,calls=0,ack=0;ScriptedInventoryOperationCallback*last=nullptr;std::function<void()>effect;
+ IEntity GetOwner(){return owner;}void GetItems(Array<IEntity>&a,int p){assert(p==1);a=items;}void GetStorages(Array<Storage*>&a,int p){assert(p==1);a=storages;}
+ bool CanSwapItemStorages(IEntity a,IEntity b){assert(a&&b&&a!=b);queries++;return allow;}
+ bool TrySwapItemStorages(IEntity a,IEntity b,ScriptedInventoryOperationCallback*cb){assert(a&&b&&a!=b);calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}};
+enum ERR_MountedAmmoTransferState{FAILED=-1,PENDING,SUCCEEDED};
+'''
+# ERR_GameMode aliases are pointer types; the static call uses the doubled class.
+code=code.replace('ERR_GameMode::Get()', 'Mode::Get()')
+main=r'''
+struct Fixture{
+ Mode mode;Ledger ledger;HQ hq;Group dg,tg;Record dr{"donor"},tr{"target"};Receipt receipt;Task task;ConvoyVehicle donor,target;Roster roster;
+ Entity truck,root,worker,gun,loaded,incoming,foreign;Physics dp,tp;Item gunItem,oldItem,newItem;Slot weaponSlot,fromSlot,toSlot;Storage carryStorage,gunStorage;Inventory carried,truckInventory;
+ Well well;Weapon weapon;Mount mount;Muzzle muzzle;Magazine oldMag,newMag;Array<IEntity>stock,spent;ERR_FiniteMagazineReservation reservation;World world;Game game_;
+ Fixture():reservation(&stock,&spent,&incoming){Mode::current=&mode;mode.ledger=&ledger;mode.hq=&hq;ledger.donor=&dg;ledger.target=&tg;ledger.d=&dr;ledger.t=&tr;
+ receipt.Records={"target","donor","2","3","4","5"};task.Receipt=&receipt;task.Vehicles.resize(6,nullptr);task.Vehicles[0]=&target;task.Vehicles[1]=&donor;
+ donor.Index=1;donor.Entity=&truck;donor.Group=&dg;target.Entity=&root;target.Group=&tg;target.Members={&worker};truck.physics=&dp;root.physics=&tp;
+ roster.group=&tg;roster.root=&root;roster.worker=&worker;gun.parent=&root;loaded.parent=&gun;gun.item=&gunItem;loaded.item=&oldItem;incoming.item=&newItem;
+ gunItem.slot=&weaponSlot;oldItem.slot=&toSlot;newItem.slot=&fromSlot;weaponSlot.attached=&gun;weaponSlot.container=&mount;mount.slot=&weaponSlot;mount.gun=&gun;mount.owner=&root;
+ weapon.owner=&gun;weapon.muzzles={&muzzle};muzzle.owner=&gun;muzzle.mag=&oldMag;muzzle.well=&well;oldMag.owner=&loaded;oldMag.rounds=25;oldMag.used=true;newMag.owner=&incoming;oldMag.well=&well;newMag.well=&well;
+ loaded.magazine=&oldMag;incoming.magazine=&newMag;toSlot.attached=&loaded;toSlot.container=&muzzle;toSlot.storage=&gunStorage;gunStorage.owner=&gun;
+ fromSlot.attached=&incoming;fromSlot.storage=&carryStorage;carryStorage.owner=&worker;carried.owner=&worker;carried.items={&incoming};carried.storages={&carryStorage};worker.inventory=&carried;
+ truckInventory.owner=&truck;truck.inventory=&truckInventory;stock={&incoming};assert(reservation.Reserve()&&reservation.Commit());game_.world=&world;game=&game_;}
+ ERR_MountedAmmoTransfer Job(){return ERR_MountedAmmoTransfer(&mode,&task,0,&roster,1,&weapon,&muzzle,&loaded,&incoming,&reservation);}
+ void Swap(){fromSlot.attached=&loaded;toSlot.attached=&incoming;oldItem.slot=&fromSlot;newItem.slot=&toSlot;muzzle.mag=&newMag;carried.items={&loaded};}
+};
+int main(){
+ {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);}
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.Swap();assert(t.Tick(1)==PENDING);f.carried.last->OnAllComplete();assert(t.Tick(2)==SUCCEEDED);}
+ // Inline success plus false native return is still accepted when exact proof agrees.
+ {Fixture f;f.carried.accept=false;f.carried.ack=1;f.carried.effect=[&]{f.Swap();};auto t=f.Job();assert(t.Tick(0)==SUCCEEDED&&!t.Unresolved()&&f.carried.calls==1);}
+ // Two transactions sharing a committed pickup can never submit two native swaps.
+ {Fixture f;auto a=f.Job();auto b=f.Job();assert(a.Tick(0)==PENDING&&b.Tick(0)==FAILED&&!b.Submitted()&&f.carried.calls==1);}
+ // No callback, even on false native return, is conservatively quarantined.
+ {Fixture f;f.carried.accept=false;auto t=f.Job();assert(t.Tick(0)==FAILED&&t.Submitted()&&t.Unresolved());f.carried.last->OnFailed();assert(!t.Unresolved());assert(t.Tick(1)==FAILED&&f.carried.calls==1);}
+ {Fixture f;auto t=f.Job();assert(t.Tick(0)==PENDING);f.carried.last->OnComplete();f.carried.last->OnFailed();assert(t.Tick(1)==FAILED&&!t.Unresolved());f.carried.last->OnAllComplete();assert(t.Tick(2)==FAILED);}
+ // A partial native failure cannot free target slots or either magazine identity.
+ {Fixture f;auto t=f.Job();t.Tick(0);f.newItem.slot=&f.toSlot;f.toSlot.attached=&f.incoming;f.carried.last->OnFailed();assert(t.Tick(1)==FAILED&&t.Unresolved());}
+ // Stop never launches/replays; late exact completion resolves quarantine only.
+ {Fixture f;auto t=f.Job();t.Stop();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0&&f.spent.Count()==1);}
+ {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);}
+ // 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;
+ case 4:f.receipt.Consumed=false;break;case 5:f.roster.valid=false;break;case 6:f.worker.seated=&f.root;break;case 7:f.worker.awake=false;break;
+ case 8:f.worker.eligible=false;break;case 9:f.hq.valid=false;break;case 10:f.dp.velocity.x=0.3;break;case 11:f.tp.angular.x=0.2;break;
+ case 12:f.target.crew=false;break;case 13:f.mount.gun=&f.foreign;break;case 14:f.muzzle.mag=nullptr;break;case 15:f.fromSlot.locked=true;break;
+ case 16:f.toSlot.locked=true;break;case 17:f.newMag.used=true;break;case 18:f.oldMag.rounds=26;break;case 19:f.newMag.rounds=25;break;
+ case 20:f.newMag.capacity=0;break;case 21:f.oldMag.rounds=-1;break;case 22:f.newMag.rounds=101;break;case 23:f.carried.allow=false;break;
+ case 24:f.truckInventory.items={&f.incoming};break;case 25:f.carried.items.resize(129,&f.foreign);break;case 26:f.carried.storages.resize(65,&f.gunStorage);break;
+ case 27:f.gunStorage.owner=&f.foreign;break;case 28:f.fromSlot.storage=&f.gunStorage;break;case 29:f.task.Vehicles[0]=&f.donor;break;
+ case 30:f.worker.inventory=&f.truckInventory;break;case 31:f.weapon.muzzles.resize(9,&f.muzzle);break;case 32:f.ledger.t=&f.dr;break;case 33:f.target.Members.clear();break;}
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0&&f.spent.Count()==1);}
+ // Partial constructor state fails closed before native inventory access.
+ for(int k=0;k<9;k++){Fixture f;
+ switch(k){case 0:f.donor.Entity=nullptr;break;case 1:f.target.Entity=nullptr;break;case 2:f.donor.Group=nullptr;break;
+ case 3:f.target.Group=nullptr;break;case 4:f.task.Vehicles[1]=nullptr;break;case 5:f.task.Vehicles[0]=nullptr;break;
+ case 6:f.roster.worker=nullptr;break;case 7:f.incoming.item=nullptr;break;case 8:f.gun.item=nullptr;break;}
+ auto t=f.Job();assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0);}
+ // A released handle cannot authorize even when another handle reclaims its exact item.
+ {Fixture f;Array<IEntity> stock{&f.incoming},spent;ERR_FiniteMagazineReservation released(&stock,&spent,&f.incoming);
+ assert(released.Reserve()&&released.ReleaseUnsubmitted());ERR_FiniteMagazineReservation other(&stock,&spent,&f.incoming);
+ assert(other.Reserve()&&other.Commit());auto t=ERR_MountedAmmoTransfer(&f.mode,&f.task,0,&f.roster,1,&f.weapon,&f.muzzle,&f.loaded,&f.incoming,&released);
+ assert(t.Tick(0)==FAILED&&!t.Submitted()&&f.carried.calls==0);}
+ // Truck is stationary but need not be beside the mount during the carried leg.
+ {Fixture f;f.truck.position={35,0,0};auto t=f.Job();assert(t.Tick(0)==PENDING&&f.carried.calls==1);}
+ // Context loss after accepted operation is terminal while exact late proof can settle quarantine.
+ for(int k=0;k<5;k++){Fixture f;auto t=f.Job();t.Tick(0);switch(k){case 0:f.target.Released=true;break;case 1:f.donor.Released=true;break;
+ case 2:f.roster.valid=false;break;case 3:f.worker.awake=false;break;case 4:f.task.Stopped=true;break;}
+ assert(t.Tick(1)==FAILED&&t.Unresolved());f.Swap();f.carried.last->OnComplete();assert(!t.Unresolved()&&t.Tick(2)==FAILED&&f.carried.calls==1);}
+ // Existing reservation has already spent the pickup. Released/foreign/duplicate handles fail.
+ {Fixture f;assert(f.reservation.CommittedFor(&f.incoming)&&!f.reservation.CommittedFor(&f.loaded));assert(!f.reservation.ReleaseUnsubmitted());}
+ {Fixture f;Array<IEntity> stock{&f.loaded},spent;ERR_FiniteMagazineReservation r(&stock,&spent,&f.loaded);assert(r.Reserve()&&r.ReleaseUnsubmitted());
+ ERR_FiniteMagazineReservation next(&stock,&spent,&f.loaded);assert(next.Reserve()&&next.Commit());assert(!r.CommittedFor(&f.loaded)&&!r.ClaimMountedSwap(&f.loaded));}
+ // Authored slot reach executes real distance/trace logic, retaining vehicle hull occlusion.
+ for(int k=0;k<7;k++){Fixture f;auto t=f.Job();switch(k){case 0:f.toSlot.point.x=4.01;break;case 1:f.toSlot.point.x=0.01;break;
+ case 2:f.toSlot.point.x=std::numeric_limits<float>::quiet_NaN();break;case 3:f.worker.position.x=std::numeric_limits<float>::infinity();break;
+ case 4:f.world.blockers={&f.root};break;case 5:f.world.blockers={&f.foreign};break;case 6:f.world.fraction=0.5;break;}
+ assert(t.Tick(0)==FAILED&&!t.Submitted());}
+ {Fixture f;f.world.blockers={&f.worker,&f.gun,&f.loaded};f.toSlot.point.x=4;auto t=f.Job();assert(t.Tick(0)==PENDING&&f.world.calls==1);}
+ // Invalid clock/server/editor guards never consume the second-leg claim.
+ for(int k=0;k<4;k++){Fixture f;auto t=f.Job();if(k==0)Replication::server=false;if(k==1)SCR_Global::editor=true;
+ assert(t.Tick(k==2?-1:k==3?std::numeric_limits<float>::quiet_NaN():0)==FAILED&&!t.Submitted());Replication::server=true;SCR_Global::editor=false;}
+}
+'''
+run(pre+translated_reservation()+code+'\n'+'\n'.join(callbacks)+main)
+print('PASS: actual carried-mounted swap, shared committed claim, physical slot reach, exact two-way proof, uncertainty and terminal cleanup')
@@ -0,0 +1,14 @@
{
"integrated": false,
"native_tested": false,
"scope": "Prepared second courier leg only, not production gameplay",
"review": "Independent exact-method regression and ownership/callback/access review found no concrete blocker under the stated caller contract.",
"required_before_integration": [
"Real successful finite pickup to an original courier, without requiring the courier to carry a matching personal firearm",
"Fresh delivered mounted-low-ammunition demand and exact original task/root/worker lineage",
"Shared finite stock/spent arrays and exclusive worker/muzzle orders",
"Strong retention while callbacks or uncertain custody remain outstanding",
"Physical approach, mounted slot access, return/reboarding and cleanup",
"Native carried-inventory swap and callback/custody qualification"
]
}
@@ -0,0 +1,14 @@
{
"addons/core/Scripts/Game/ERR/10_AI/ERR_FiniteMagazineReservation.c": {
"base_sha256": "062e922a4e3e9f1465feb76e7406cf4cb4210b684e93d8bfe148b4ed50195fbb",
"proposed_sha256": "8278590ced1936d618905b0cbd0b5aebddda238d426a6a2a52e455fc36bbb502"
},
"addons/core/Scripts/Game/ERR/10_AI/ERR_MountedAmmoTransfer.c": {
"base_sha256": null,
"proposed_sha256": "4e20f259c275025e143686f7327dcc7d62e3e2be4950701bda663c3eecbf4c99"
},
"tests/test_mounted_ammo_transfer.py": {
"base_sha256": null,
"proposed_sha256": "e9e0d3916110c6476fbe94df1a27556ffea933d07911e024bca2d82fedc5daae"
}
}