Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
426 lines
43 KiB
Diff
426 lines
43 KiB
Diff
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c
|
|
@@ -96,6 +96,8 @@
|
|
protected bool m_PickedUp;
|
|
protected bool m_DeliveryClaimed;
|
|
protected bool m_Imported;
|
|
+ protected bool m_MineCargo;
|
|
+ bool MineCargo() { return m_MineCargo; }
|
|
void ERR_GroundDepotLoading(ERR_HQ hq, ERR_GameMode mode) { m_HQ = hq; m_Mode = mode; }
|
|
bool Busy() { return m_State != 0; }
|
|
bool Unresolved() { return m_Move && m_Move.Unresolved(); }
|
|
@@ -138,6 +140,7 @@
|
|
}
|
|
protected bool Valid()
|
|
{
|
|
+ if (!ERR_GroundDepotTransfer.Cargo(m_Item, m_MineCargo)) return false;
|
|
if (!Active() || !m_Service || m_Service.Task() != m_Task || !m_Service.DepotCurrent() || m_Service.DepotTruck() != m_Truck) return false;
|
|
if (!m_Task || !m_Task.Receipt || !m_Task.Receipt.Consumed || m_Task.Vehicles.Count() != 6 || m_Task.Vehicles[1] != m_Truck) return false;
|
|
ERR_SupplyConvoyTask supply = ERR_SupplyConvoyTask.Cast(m_Task);
|
|
@@ -174,7 +177,7 @@
|
|
array<IEntity> items = {}; ERR_Inventory.Items(inventory, items);
|
|
return items.Count() <= 128 && items.Contains(item);
|
|
}
|
|
- protected bool SelectWorker(ERR_HQUnit crew, IEntity item)
|
|
+ protected bool SelectWorker(ERR_HQUnit crew, IEntity item, bool mineCargo = false)
|
|
{
|
|
if (!item || !Quiet(crew) || crew.HomeGuard || crew.ReservedForSupport || crew.LogisticsReservation > 0) return false;
|
|
if (crew.Assignment || crew.Task || crew.PendingTask || crew.PendingOrder || crew.Downlink || crew.Waypoint) return false;
|
|
@@ -186,7 +189,7 @@
|
|
if (!actor || !crew.StatusSampler.CanAttribute(slot, actor, false) || !ERR_EnemyEquipmentRules.Eligible(m_Mode, crew.Group, actor) || CompartmentAccessComponent.GetVehicleIn(actor)) continue;
|
|
SCR_ChimeraAIAgent agent = SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(actor));
|
|
if (!agent || !agent.m_UtilityComponent || !agent.m_UtilityComponent.m_AIInfo) continue;
|
|
- if (!ERR_InfantryAmmoTransfer.Compatible(actor, item)) continue;
|
|
+ if (!mineCargo && !ERR_InfantryAmmoTransfer.Compatible(actor, item)) continue;
|
|
m_Worker = actor; m_WorkerSlot = slot; m_Utility = agent.m_UtilityComponent; return true;
|
|
}
|
|
return false;
|
|
@@ -198,7 +201,7 @@
|
|
for (int index = 0; index < m_HQ.GroundSupplyCount() && index < 20; index++)
|
|
{
|
|
ERR_InfantryResupply service = m_HQ.GroundSupplyAt(index);
|
|
- if (!service || !service.DepotNeeded()) continue;
|
|
+ if (!service || (!service.DepotNeeded() && !service.DepotMineNeeded())) continue;
|
|
ERR_ConvoyVehicle truck = service.DepotTruck();
|
|
if (!truck || truck.SupplyToken > 0) continue;
|
|
ERR_HQUnit donor = m_HQ.GroundUnit(truck.Group);
|
|
@@ -208,28 +211,32 @@
|
|
for (int siteIndex = 0; siteIndex < m_Mode.Sites().Count() && siteIndex < 64; siteIndex++)
|
|
{
|
|
ERR_SiteRuntime site = m_Mode.Sites().At(siteIndex);
|
|
- if (!site || !site.Definition || m_Mode.Sites().DepotMagazineCount(site) <= 0) continue;
|
|
+ if (!site || !site.Definition || m_Mode.Sites().DepotItemCount(site) <= 0) continue;
|
|
// The camp is an authored own location; only the truck position is reported.
|
|
if (vector.DistanceXZ(site.Definition.Position, report.Position) > 150) continue;
|
|
ERR_HQUnit crew = m_HQ.GroundUnit(site.Responders.Group);
|
|
- for (int itemIndex = 0; itemIndex < m_Mode.Sites().DepotMagazineCount(site) && itemIndex < 96; itemIndex++)
|
|
+ for (int itemIndex = 0; itemIndex < m_Mode.Sites().DepotItemCount(site) && itemIndex < 96; itemIndex++)
|
|
{
|
|
- IEntity item = m_Mode.Sites().DepotMagazine(site, itemIndex);
|
|
- if (!m_Original.Contains(item) || m_Spent.Contains(item) || !ERR_GroundDepotTransfer.Magazine(item) || !Stored(item, site.Container)) continue;
|
|
+ IEntity item = m_Mode.Sites().DepotItem(site, itemIndex);
|
|
+ bool mineCargo = ERR_InfantryMineTransfer.ReadyMine(item);
|
|
+ if (mineCargo) { if (!service.DepotMineNeeded()) continue; }
|
|
+ else if (!service.DepotNeeded()) continue;
|
|
+ if (!m_Original.Contains(item) || m_Spent.Contains(item) || !ERR_GroundDepotTransfer.Cargo(item, mineCargo) || !Stored(item, site.Container)) continue;
|
|
// Select an original worker for this exact item; an incompatible first
|
|
// member must not hide a later eligible carrier of finite camp stock.
|
|
- if (!SelectWorker(crew, item)) continue;
|
|
+ if (!SelectWorker(crew, item, mineCargo)) continue;
|
|
int token = 300000 + m_Attempts + 1;
|
|
- if (!m_HQ.ReserveGroundDepot(crew, donor, service, token)) continue;
|
|
+ if (!m_HQ.ReserveGroundDepot(crew, donor, service, token, mineCargo)) continue;
|
|
m_Reservation = new ERR_FiniteMagazineReservation(m_Original, m_Spent, item);
|
|
if (!m_Reservation.Reserve()) { m_HQ.ReleaseGroundDepot(crew, donor, truck, token); continue; }
|
|
+ m_MineCargo = mineCargo;
|
|
m_Attempts++; m_Token = token; m_Crew = crew; m_Donor = donor; m_Service = service; m_Task = service.Task(); m_Truck = truck;
|
|
m_Site = site; m_Cache = site.Container; m_CachePosition = m_Cache.GetOrigin(); m_Item = item;
|
|
m_CacheInventory = InventoryStorageManagerComponent.Cast(m_Cache.FindComponent(InventoryStorageManagerComponent));
|
|
m_CrewRecord = m_Mode.Forces().FindGroup(crew.Group);
|
|
m_PickedUp = false; m_DeliveryClaimed = false; m_Imported = false; m_HeldAt = -1;
|
|
m_Deadline = Math.Min(now + 180000, report.ObservedAt + 240000); m_State = 1;
|
|
- m_Order = m_HQ.Downlink(donor.Group, "DEPOT HOLD", "hold original truck for one finite camp magazine", report.MessageId);
|
|
+ m_Order = m_HQ.Downlink(donor.Group, "DEPOT HOLD", "hold original truck for one finite camp item", report.MessageId);
|
|
if (!m_Order) Abort("truck radio unavailable"); else Note("one original camp item reserved; awaiting truck hold radio");
|
|
return;
|
|
}
|
|
@@ -260,7 +267,7 @@
|
|
ClearAction();
|
|
if (m_Reservation) m_Reservation.ReleaseUnsubmitted();
|
|
if (m_HQ) m_HQ.ReleaseGroundDepot(m_Crew, m_Donor, m_Truck, m_Token);
|
|
- m_State = 0; m_Token = 0; m_Order = null; m_Move = null; m_Reservation = null;
|
|
+ m_State = 0; m_MineCargo = false; m_Token = 0; m_Order = null; m_Move = null; m_Reservation = null;
|
|
m_Crew = null; m_Donor = null; m_Service = null; m_Task = null; m_Truck = null;
|
|
m_Worker = null; m_Utility = null; m_Site = null; m_Cache = null; m_Item = null; m_CacheInventory = null; m_CrewRecord = null;
|
|
}
|
|
@@ -274,18 +281,20 @@
|
|
void Stop() { if (m_Stopped) return; m_Stopped = true; Abort("depot service stopped; any submitted uncertainty retains original claims"); }
|
|
bool TransferValid(ERR_GroundDepotTransfer operation)
|
|
{
|
|
- if (!operation || operation != m_Move || operation.Item() != m_Item || !Valid() || !Stationary() || !m_Truck.SupplyHeld) return false;
|
|
+ if (!operation || operation != m_Move || operation.Item() != m_Item || operation.MineCargo() != m_MineCargo || !Valid() || !Stationary() || !m_Truck.SupplyHeld) return false;
|
|
if (operation.Delivery()) return m_PickedUp && m_State == 5 && ERR_GroundDepotAccess.Clear(m_Truck.Entity, m_Worker);
|
|
return !m_PickedUp && m_State == 4 && ERR_GroundDepotAccess.Clear(m_Cache, m_Worker);
|
|
}
|
|
bool ClaimDelivery(ERR_GroundDepotTransfer operation)
|
|
{
|
|
if (!TransferValid(operation) || !operation.Delivery() || m_DeliveryClaimed || !m_Reservation.CommittedFor(m_Item)) return false;
|
|
+ if (m_MineCargo) { if (!m_Service.DepotMineNeeded()) return false; }
|
|
+ else if (!m_Service.DepotNeeded()) return false;
|
|
m_DeliveryClaimed = true; return true;
|
|
}
|
|
- bool DeliveredTo(ERR_InfantryResupply service, IEntity item)
|
|
- {
|
|
- return !m_Imported && m_State == 5 && service == m_Service && item == m_Item && m_Move && m_Move.Delivery()
|
|
+ bool DeliveredTo(ERR_InfantryResupply service, IEntity item, bool mineCargo = false)
|
|
+ {
|
|
+ return mineCargo == m_MineCargo && !m_Imported && m_State == 5 && service == m_Service && item == m_Item && m_Move && m_Move.Delivery() && m_Move.MineCargo() == m_MineCargo
|
|
&& m_Move.Confirmed() && m_Reservation && m_Reservation.CommittedFor(item) && Valid();
|
|
}
|
|
void Tick(float now)
|
|
@@ -315,7 +324,7 @@
|
|
if (!ERR_InfantryResupplyRules.Fresh(report, now) || report.ObservedAt < m_HeldAt) return;
|
|
if (vector.DistanceXZ(report.Position, m_Site.Definition.Position) > 150) { Abort("reported stopped truck outside camp service range"); return; }
|
|
m_TruckPosition = report.Position + "3 0 0";
|
|
- m_Order = m_HQ.Downlink(m_Crew.Group, "DEPOT COURIER", string.Format("carry one existing magazine from camp to stopped truck at %1", m_TruckPosition), report.MessageId);
|
|
+ m_Order = m_HQ.Downlink(m_Crew.Group, "DEPOT COURIER", string.Format("carry one existing item from camp to stopped truck at %1", m_TruckPosition), report.MessageId);
|
|
if (!m_Order) { Abort("porter radio unavailable"); return; }
|
|
m_State = 3; return;
|
|
}
|
|
@@ -333,7 +342,7 @@
|
|
if (delivery) { source = m_Worker; destination = m_Truck.Entity; }
|
|
IEntity access = m_Cache; if (delivery) access = m_Truck.Entity;
|
|
if (!ERR_GroundDepotAccess.Clear(access, m_Worker)) return;
|
|
- m_Move = new ERR_GroundDepotTransfer(this, source, destination, m_Item, m_Reservation, delivery);
|
|
+ m_Move = new ERR_GroundDepotTransfer(this, source, destination, m_Item, m_Reservation, delivery, m_MineCargo);
|
|
}
|
|
int moved = m_Move.Tick(now);
|
|
if (moved == 0) return;
|
|
@@ -344,9 +353,12 @@
|
|
if (!Walk(m_TruckPosition)) { Abort("porter cannot walk to reported truck"); return; }
|
|
m_State = 5; return;
|
|
}
|
|
- if (!m_Service.AdmitDepot(this, m_Item)) { Abort("proved physical delivery could not enter finite truck allowance"); return; }
|
|
+ bool admitted;
|
|
+ if (m_MineCargo) admitted = m_Service.AdmitDepotMine(this, m_Item);
|
|
+ else admitted = m_Service.AdmitDepot(this, m_Item);
|
|
+ if (!admitted) { Abort("proved physical delivery could not enter finite truck allowance"); return; }
|
|
m_Imported = true; m_Delivered++; m_Move = null;
|
|
- Note("one original camp magazine physically delivered and admitted; porter returning to camp");
|
|
+ Note("one original camp item physically delivered and admitted; porter returning to camp");
|
|
if (!Walk(m_CachePosition + "2 0 0")) { Cleanup(); return; }
|
|
m_State = 6;
|
|
}
|
|
--- a/addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c
|
|
+++ b/addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotTransfer.c
|
|
@@ -23,6 +23,7 @@
|
|
protected ref ERR_FiniteMagazineReservation m_Reservation;
|
|
protected ref ERR_GroundDepotCallback m_Callback;
|
|
protected bool m_Delivery;
|
|
+ protected bool m_MineCargo;
|
|
protected bool m_Submitted;
|
|
protected int m_Ack;
|
|
protected int m_State;
|
|
@@ -30,9 +31,9 @@
|
|
protected int m_Capacity;
|
|
protected float m_Started = -1;
|
|
protected float m_Last = -1;
|
|
- void ERR_GroundDepotTransfer(ERR_GroundDepotLoading owner, IEntity source, IEntity destination, IEntity item, ERR_FiniteMagazineReservation reservation, bool delivery)
|
|
+ void ERR_GroundDepotTransfer(ERR_GroundDepotLoading owner, IEntity source, IEntity destination, IEntity item, ERR_FiniteMagazineReservation reservation, bool delivery, bool mineCargo = false)
|
|
{
|
|
- m_Owner = owner; m_From = source; m_To = destination; m_Item = item; m_Reservation = reservation; m_Delivery = delivery;
|
|
+ m_Owner = owner; m_From = source; m_To = destination; m_Item = item; m_Reservation = reservation; m_Delivery = delivery; m_MineCargo = mineCargo;
|
|
if (!source || !destination || !item) return;
|
|
m_Source = InventoryStorageManagerComponent.Cast(source.FindComponent(InventoryStorageManagerComponent));
|
|
m_Destination = InventoryStorageManagerComponent.Cast(destination.FindComponent(InventoryStorageManagerComponent));
|
|
@@ -51,6 +52,19 @@
|
|
return magazine && !magazine.IsUsed() && magazine.GetMagazineWell() && magazine.GetAmmoCount() > 0
|
|
&& magazine.GetAmmoCount() <= magazine.GetMaxAmmoCount() && magazine.GetMaxAmmoCount() <= 100000;
|
|
}
|
|
+ static bool Cargo(IEntity item, bool mineCargo = false)
|
|
+ {
|
|
+ if (mineCargo) return ERR_InfantryMineTransfer.ReadyMine(item);
|
|
+ return Magazine(item);
|
|
+ }
|
|
+ protected bool CargoUnchanged()
|
|
+ {
|
|
+ if (!Cargo(m_Item, m_MineCargo)) return false;
|
|
+ if (m_MineCargo) return true;
|
|
+ BaseMagazineComponent magazine = BaseMagazineComponent.Cast(m_Item.FindComponent(BaseMagazineComponent));
|
|
+ return magazine.GetAmmoCount() == m_Rounds && magazine.GetMaxAmmoCount() == m_Capacity;
|
|
+ }
|
|
+ bool MineCargo() { return m_MineCargo; }
|
|
bool Submitted() { return m_Submitted; }
|
|
bool Delivery() { return m_Delivery; }
|
|
IEntity Item() { return m_Item; }
|
|
@@ -102,18 +116,16 @@
|
|
return true;
|
|
}
|
|
void Stop() { if (m_State == 0) m_State = -1; }
|
|
- bool Confirmed() { return m_State == 1 && m_Ack == 1 && PhysicalProof(); }
|
|
+ bool Confirmed() { return m_State == 1 && m_Ack == 1 && CargoUnchanged() && PhysicalProof(); }
|
|
int Tick(float now)
|
|
{
|
|
if (m_State != 0) return m_State;
|
|
if (!ERR_ForceRules.Finite(now) || now < 0 || now < m_Last) { Stop(); return m_State; }
|
|
if (m_Started < 0) m_Started = now;
|
|
m_Last = now;
|
|
- if (now - m_Started >= 8000 || !m_Owner || !m_Owner.TransferValid(this) || !Magazine(m_Item)) { Stop(); return m_State; }
|
|
+ if (now - m_Started >= 8000 || !m_Owner || !m_Owner.TransferValid(this) || !CargoUnchanged()) { Stop(); return m_State; }
|
|
if (!m_From || !m_To || m_From.FindComponent(InventoryStorageManagerComponent) != m_Source
|
|
|| m_To.FindComponent(InventoryStorageManagerComponent) != m_Destination) { Stop(); return m_State; }
|
|
- BaseMagazineComponent magazine = BaseMagazineComponent.Cast(m_Item.FindComponent(BaseMagazineComponent));
|
|
- if (magazine.GetAmmoCount() != m_Rounds || magazine.GetMaxAmmoCount() != m_Capacity) { Stop(); return m_State; }
|
|
if (!m_Submitted)
|
|
{
|
|
if (!m_Reservation || !OriginalProof()) { Stop(); return m_State; }
|
|
@@ -129,7 +141,7 @@
|
|
}
|
|
// Native callbacks may synchronously cancel the owner or this exact leaf.
|
|
if (m_State != 0) return m_State;
|
|
- if (!m_Owner || !m_Owner.TransferValid(this)) { Stop(); return m_State; }
|
|
+ if (!m_Owner || !m_Owner.TransferValid(this) || !CargoUnchanged()) { Stop(); return m_State; }
|
|
if (m_Ack < 0) { Stop(); return m_State; }
|
|
if (m_Ack == 1 && PhysicalProof()) m_State = 1;
|
|
return m_State;
|
|
--- a/tests/test_ground_depot_loading.py
|
|
+++ b/tests/test_ground_depot_loading.py
|
|
@@ -4,9 +4,10 @@
|
|
from policy_harness import ROOT,body,run
|
|
BASE=Path(os.environ.get('ERR_DEPOT_ROOT',ROOT))
|
|
P=BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_GroundDepotLoading.c'
|
|
-S=BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'
|
|
+S=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',BASE/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
|
|
source=P.read_text()
|
|
valid=body(P,'protected bool Valid(')
|
|
+assert 'ERR_GroundDepotTransfer.Cargo(m_Item, m_MineCargo)' in valid
|
|
assert 'm_Service.DepotCurrent()' in valid and 'DepotAvailable()' not in valid and 'DepotNeeded()' not in valid
|
|
for forbidden in ('Teleport(', 'TrySpawn', 'SetAmmoCount', 'SetOrigin(', 'SetTransform('):assert forbidden not in source
|
|
|
|
@@ -15,12 +16,13 @@
|
|
for t in ('ERR_HQUnit','ERR_InfantryResupply','ERR_ConvoyVehicle','ERR_HQWatchEntry','ERR_SiteRuntime','ERR_GroundDepotTransfer','ERR_GroundDepotLoading','ERR_FiniteMagazineReservation','SCR_ChimeraAIAgent'):
|
|
s=re.sub(r'\b'+t+r' (\w+)',t+r'* \1',s)
|
|
s=s.replace('ERR_FiniteMagazineReservation(m_Original, m_Spent, item)','ERR_FiniteMagazineReservation(&m_Original, &m_Spent, item)')
|
|
- for ns in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_GroundDepotTransfer','ERR_InfantryAmmoTransfer','ERR_GroundDepotAccess','ERR_Actors','ERR_EnemyEquipmentRules','CompartmentAccessComponent','Math'):
|
|
+ for ns in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_GroundDepotTransfer','ERR_InfantryAmmoTransfer','ERR_GroundDepotAccess','ERR_Actors','ERR_EnemyEquipmentRules','CompartmentAccessComponent','ERR_InfantryMineTransfer','Math'):
|
|
s=s.replace(ns+'.',ns+'::')
|
|
for n in ('m_HQ','m_Mode','m_Service','m_Move','m_Reservation','m_Order','m_Truck','m_Crew','m_Donor','m_Worker','m_Site','m_Cache','service','truck','donor','report','site','crew','operation','actor','agent'):
|
|
s=re.sub(r'\b'+n+r'\.',n+'->',s)
|
|
s=s.replace('crew->StatusSampler.', 'crew->StatusSampler->').replace('agent->m_UtilityComponent.', 'agent->m_UtilityComponent->')
|
|
s=s.replace('SCR_ChimeraAIAgent.Cast(SCR_AIUtils.GetAIAgent(actor))', 'actor->agent')
|
|
+ s=s.replace('InventoryItemComponent.Cast(item.FindComponent(InventoryItemComponent))','item->carried').replace('InventoryItemComponent carried','InventoryItem* carried').replace('carried.GetParentSlot()', 'carried->GetParentSlot()').replace('GetParentSlot().','GetParentSlot()->')
|
|
s=s.replace('Sites().','Sites()->').replace('Watch().','Watch()->').replace('Forces().','Forces()->')
|
|
s=s.replace('site->Responders.Group','site->Responders->Group').replace('site->Definition.Position','site->Definition->Position').replace('m_Site->Definition.Position','m_Site->Definition->Position')
|
|
s=s.replace('m_Truck->Entity.GetOrigin()', 'm_Truck->Entity->GetOrigin()')
|
|
@@ -28,8 +30,8 @@
|
|
s=s.replace('InventoryStorageManagerComponent.Cast(m_Cache->FindComponent(InventoryStorageManagerComponent))','m_Cache->inventory')
|
|
s=s.replace('"3 0 0"','Vec{3}').replace('"2 0 0"','Vec{2}')
|
|
return s
|
|
-methods='\n'.join(cpp(body(P,x)) for x in ('protected bool SelectWorker(', 'void Plan(', 'void Tick(', 'protected void Abort(', 'void Stop(', 'bool TransferValid(', 'bool ClaimDelivery(', 'bool DeliveredTo('))
|
|
-admit=cpp(body(S,'bool AdmitDepot(')).replace('job.','job->').replace('m_Stock.Insert(item); Audit(', 'm_Stock.Insert(item); Audit(')
|
|
+methods='\n'.join(cpp(body(P,x)) for x in ('bool MineCargo(', 'protected bool SelectWorker(', 'void Plan(', 'void Tick(', 'protected void Abort(', 'void Stop(', 'bool TransferValid(', 'bool ClaimDelivery(', 'bool DeliveredTo('))
|
|
+admit=(cpp(body(S,'bool AdmitDepot('))+cpp(body(S,'bool AdmitDepotMine('))).replace('job.','job->').replace('m_Stock.Insert(item); Audit(', 'm_Stock.Insert(item); Audit(')
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
@@ -42,13 +44,14 @@
|
|
template<class...T>string Format(string s,T...){return s;}
|
|
struct ERR_ForceRules{static bool Finite(float f){return std::isfinite(f);}};
|
|
struct Math{static float Min(float a,float b){return std::min(a,b);}};
|
|
+struct Entity;struct Slot{Entity*attached=nullptr;Entity*GetAttachedEntity(){return attached;}};struct InventoryItem{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};
|
|
struct Group{int faction=1;};struct Inventory{};struct Utility{bool m_AIInfo=true;};struct SCR_ChimeraAIAgent{Utility*m_UtilityComponent=nullptr;};
|
|
-struct Entity{Vec pos;Inventory*inventory=nullptr;bool stock=true,compatible=true,alive=true,player=false,aboard=false,attributed=true,equipmentEligible=true;int faction=1,well=1,rounds=30,maxRounds=30;Group*group=nullptr;SCR_ChimeraAIAgent*agent=nullptr;Vec GetOrigin(){return pos;}};using IEntity=Entity*;
|
|
+struct Entity{Vec pos;Inventory*inventory=nullptr;bool stock=true,compatible=true,alive=true,player=false,aboard=false,attributed=true,equipmentEligible=true,mine=false,armed=false,defaultArmed=false,collision=true;InventoryItem*carried=nullptr;Entity*parent=nullptr;int faction=1,well=1,rounds=30,maxRounds=30;Group*group=nullptr;SCR_ChimeraAIAgent*agent=nullptr;Vec GetOrigin(){return pos;}};using IEntity=Entity*;
|
|
struct Sampler{Array<IEntity>members;Group*group=nullptr;bool owns=true;int InitialStrength(){return members.Count();}bool OwnsRoster(Group*g){return owns&&g==group;}IEntity InitialMember(int i){return members[i];}bool CanAttribute(int i,IEntity a,bool){return a&&members[i]==a&&a->attributed&&a->group==group&&!a->player&&a->faction==group->faction;}};
|
|
struct ERR_Actors{static bool CanFight(IEntity a){return a&&a->alive;}};
|
|
struct CompartmentAccessComponent{static bool GetVehicleIn(IEntity a){return a&&a->aboard;}};
|
|
struct Definition{Vec Position;};struct ERR_SiteRuntime{struct Definition*Definition=nullptr;ERR_SiteRuntime*Responders=nullptr;struct Group*Group=nullptr;IEntity Container=nullptr;Array<IEntity>items;};
|
|
-struct Sites{Array<ERR_SiteRuntime*>sites;int Count(){return sites.Count();}ERR_SiteRuntime*At(int i){return sites[i];}int DepotMagazineCount(ERR_SiteRuntime*s){return s?s->items.Count():0;}IEntity DepotMagazine(ERR_SiteRuntime*s,int i){return s->items[i];}};
|
|
+struct Sites{Array<ERR_SiteRuntime*>sites;int Count(){return sites.Count();}ERR_SiteRuntime*At(int i){return sites[i];}int DepotItemCount(ERR_SiteRuntime*s){return s?s->items.Count():0;}IEntity DepotItem(ERR_SiteRuntime*s,int i){return s->items[i];}};
|
|
struct ERR_HQUnit{struct Group*Group=nullptr;int ForceSourceId=0,LogisticsReservation=0;float NextStatus=0;bool eligible=true,HomeGuard=false,ReservedForSupport=false,Assignment=false,Task=false,PendingTask=false,PendingOrder=false,Downlink=false,Waypoint=false;Sampler*StatusSampler=nullptr;};
|
|
struct ERR_ConvoyTask{};
|
|
struct ERR_ConvoyVehicle{struct Group*Group=nullptr;IEntity Entity=nullptr;int SupplyToken=0;bool SupplyHeld=false;int clears=0;void ClearOrder(){clears++;}};
|
|
@@ -63,36 +66,50 @@
|
|
struct Route{int outcome=1,calls=0;int Advance(float,void*){calls++;return outcome;}};
|
|
struct HQ{Array<ERR_InfantryResupply*>services;ERR_HQUnit*crew=nullptr,*donor=nullptr;struct Watch watch;Route route;bool claim=true,radio=true;int orders=0,releases=0,claims=0;
|
|
int GroundSupplyCount(){return services.Count();}ERR_InfantryResupply*GroundSupplyAt(int i){return services[i];}ERR_HQUnit*GroundUnit(Group*g){return crew&&crew->Group==g?crew:donor&&donor->Group==g?donor:nullptr;}struct Watch*Watch(){return &watch;}void*Journal(){return this;}
|
|
- bool ReserveGroundDepot(ERR_HQUnit*,ERR_HQUnit*,ERR_InfantryResupply*,int);
|
|
+ bool ReserveGroundDepot(ERR_HQUnit*,ERR_HQUnit*,ERR_InfantryResupply*,int,bool=false);
|
|
void ReleaseGroundDepot(ERR_HQUnit*c,ERR_HQUnit*d,ERR_ConvoyVehicle*t,int token){releases++;if(c&&c->LogisticsReservation==token)c->LogisticsReservation=0;if(d&&d->LogisticsReservation==token)d->LogisticsReservation=0;if(t&&t->SupplyToken==token){t->SupplyToken=0;t->SupplyHeld=false;}}
|
|
Route*Downlink(Group*,string,string,int){orders++;return radio?&route:nullptr;}
|
|
};
|
|
// Native compatibility boundary double: pair-specific carried weapon well,
|
|
// supported weapon flag, and sane positive ammo. Real Compatible's full native
|
|
// weapon/muzzle/type/ownership logic remains covered by the transfer/planning suites.
|
|
-struct ERR_InfantryAmmoTransfer{static bool Compatible(IEntity a,IEntity i){return a&&i&&i->compatible&&a->compatible&&a->well>0&&a->well==i->well&&i->rounds>0&&i->maxRounds>0&&i->rounds<=i->maxRounds;}};
|
|
+struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity i,IEntity owner){return i&&i->parent==owner;}static bool Compatible(IEntity a,IEntity i){return a&&i&&i->compatible&&a->compatible&&a->well>0&&a->well==i->well&&i->rounds>0&&i->maxRounds>0&&i->rounds<=i->maxRounds;}};
|
|
+struct ERR_InfantryMineTransfer{static bool ReadyMine(IEntity i){return i&&i->mine&&i->alive&&!i->armed&&!i->defaultArmed&&i->collision;}};
|
|
struct ERR_GroundDepotAccess{static inline bool clear=true;static bool Clear(IEntity,IEntity){return clear;}};
|
|
-struct ERR_GroundDepotTransfer{static inline int constructed=0;bool delivery=false,submitted=true,unknown=true,confirmed=false;int result=0;IEntity item;ERR_FiniteMagazineReservation*r;
|
|
- ERR_GroundDepotTransfer(ERR_GroundDepotLoading*,IEntity,IEntity,IEntity i,ERR_FiniteMagazineReservation*q,bool d):delivery(d),item(i),r(q){constructed++;r->committed=true;}
|
|
- static bool Magazine(IEntity i){return i&&i->stock;}bool Delivery(){return delivery;}IEntity Item(){return item;}bool Unresolved(){return submitted&&unknown;}bool Confirmed(){return confirmed;}void Stop(){result=-1;}int Tick(float){return result;}
|
|
+struct ERR_GroundDepotTransfer{static inline int constructed=0;bool mineCargo=false,delivery=false,submitted=true,unknown=true,confirmed=false;int result=0;IEntity item;ERR_FiniteMagazineReservation*r;
|
|
+ ERR_GroundDepotTransfer(ERR_GroundDepotLoading*,IEntity,IEntity,IEntity i,ERR_FiniteMagazineReservation*q,bool d,bool m=false):mineCargo(m),delivery(d),item(i),r(q){constructed++;r->committed=true;}
|
|
+ static bool Magazine(IEntity i){return i&&i->stock&&!i->mine;}static bool Cargo(IEntity i,bool mine=false){return mine?ERR_InfantryMineTransfer::ReadyMine(i):Magazine(i);}bool MineCargo(){return mineCargo;}bool Delivery(){return delivery;}IEntity Item(){return item;}bool Unresolved(){return submitted&&unknown;}bool Confirmed(){return confirmed&&Cargo(item,mineCargo);}void Stop(){result=-1;}int Tick(float){return result;}
|
|
};
|
|
-struct ERR_InfantryResupply{bool available=true,needed=true;int m_DepotImports=0;ERR_ConvoyVehicle*truck=nullptr;ERR_ConvoyTask task;Array<IEntity>m_Stock,m_Spent;ERR_ConvoyVehicle*m_Truck=nullptr;
|
|
- bool DepotAvailable(){return available&&m_Stock.Count()<96;}bool DepotNeeded(){return DepotAvailable()&&needed;}ERR_ConvoyVehicle*DepotTruck(){return available?truck:nullptr;}ERR_ConvoyTask*Task(){return &task;}bool Contains(IEntity,IEntity i){return i!=nullptr;}void Audit(string){}bool AdmitDepot(ERR_GroundDepotLoading*,IEntity);
|
|
+struct ERR_InfantryResupply{bool available=true,needed=true,mineNeeded=false;int m_DepotImports=0,m_DepotMineImports=0;ERR_ConvoyVehicle*truck=nullptr;ERR_ConvoyTask task;Array<IEntity>m_Stock,m_MineStock,m_Spent;ERR_ConvoyVehicle*m_Truck=nullptr;
|
|
+ bool DepotAvailable(){return available&&m_Stock.Count()<96;}bool DepotNeeded(){return DepotAvailable()&&needed;}bool DepotMineNeeded(){return available&&mineNeeded&&m_MineStock.Count()<96&&m_DepotMineImports==0;}ERR_ConvoyVehicle*DepotTruck(){return available?truck:nullptr;}ERR_ConvoyTask*Task(){return &task;}bool Contains(IEntity,IEntity i){return i!=nullptr;}void Audit(string){}bool AdmitDepot(ERR_GroundDepotLoading*,IEntity);bool AdmitDepotMine(ERR_GroundDepotLoading*,IEntity);
|
|
};
|
|
-struct ERR_GroundDepotLoading{HQ*m_HQ;Mode*m_Mode;Array<IEntity>m_Original,m_Spent;bool active=true,valid=true,stationary=true,canWalk=true,m_Stopped=false,m_Captured=false,m_PickedUp=false,m_DeliveryClaimed=false,m_Imported=false;int m_State=0,m_Attempts=0,m_Delivered=0,m_Token=0,m_WorkerSlot=0,walks=0,cleanups=0,notes=0;float m_NextPlan=0,m_Deadline=0,m_Last=-1,m_NextTick=0,m_HeldAt=-1;Vec m_CachePosition,m_TruckPosition;
|
|
+struct ERR_GroundDepotLoading{HQ*m_HQ;Mode*m_Mode;Array<IEntity>m_Original,m_Spent;bool active=true,valid=true,stationary=true,canWalk=true,m_Stopped=false,m_Captured=false,m_PickedUp=false,m_DeliveryClaimed=false,m_Imported=false,m_MineCargo=false;int m_State=0,m_Attempts=0,m_Delivered=0,m_Token=0,m_WorkerSlot=0,walks=0,cleanups=0,notes=0;float m_NextPlan=0,m_Deadline=0,m_Last=-1,m_NextTick=0,m_HeldAt=-1;Vec m_CachePosition,m_TruckPosition;
|
|
Utility*m_Utility=nullptr;ERR_HQUnit*m_Crew=nullptr,*m_Donor=nullptr;ERR_SiteRuntime*m_Site=nullptr;IEntity m_Worker=nullptr,m_Cache=nullptr,m_Item=nullptr;Inventory*m_CacheInventory=nullptr;void*m_CrewRecord=nullptr;ERR_InfantryResupply*m_Service=nullptr;ERR_ConvoyTask*m_Task=nullptr;ERR_ConvoyVehicle*m_Truck=nullptr;Route*m_Order=nullptr;ERR_GroundDepotTransfer*m_Move=nullptr;ERR_FiniteMagazineReservation*m_Reservation=nullptr;
|
|
- bool Active(){return active&&!m_Stopped;}bool Busy(){return m_State!=0;}bool Unresolved(){return m_Move&&m_Move->Unresolved();}bool Valid(){return Active()&&valid&&(!m_Service||m_Service->available);}bool Stationary(){return stationary;}bool Quiet(ERR_HQUnit*u){return u&&u->eligible;}bool Stored(IEntity,IEntity){return true;}void Capture(){m_Captured=true;}void Note(string){notes++;}void ClearAction(){}bool Walk(Vec){walks++;return canWalk;}
|
|
+ bool Active(){return active&&!m_Stopped;}bool Busy(){return m_State!=0;}bool Unresolved(){return m_Move&&m_Move->Unresolved();}bool Valid(){return Active()&&valid&&(!m_Service||m_Service->available)&&ERR_GroundDepotTransfer::Cargo(m_Item,m_MineCargo);}bool Stationary(){return stationary;}bool Quiet(ERR_HQUnit*u){return u&&u->eligible;}bool Stored(IEntity,IEntity){return true;}void Capture(){m_Captured=true;}void Note(string){notes++;}void ClearAction(){}bool Walk(Vec){walks++;return canWalk;}
|
|
void Cleanup(){cleanups++;if(m_Reservation)m_Reservation->ReleaseUnsubmitted();m_HQ->ReleaseGroundDepot(m_Crew,m_Donor,m_Truck,m_Token);m_State=0;m_Token=0;m_Move=nullptr;m_Order=nullptr;}
|
|
METHODS
|
|
};
|
|
ADMISSION
|
|
-bool HQ::ReserveGroundDepot(ERR_HQUnit*c,ERR_HQUnit*d,ERR_InfantryResupply*s,int token){if(!claim||c->LogisticsReservation||d->LogisticsReservation||s->truck->SupplyToken)return false;claims++;c->LogisticsReservation=d->LogisticsReservation=s->truck->SupplyToken=token;return true;}
|
|
-struct F{Mode mode;HQ hq;ERR_GroundDepotLoading job;Group cg,dg;ERR_HQUnit crew,donor;Definition def;ERR_SiteRuntime site,responders;Entity worker,second,cache,truck,item;Inventory inv;Utility workerUtility,secondUtility;SCR_ChimeraAIAgent workerAgent,secondAgent;Sampler sampler;ERR_ConvoyVehicle vehicle;ERR_InfantryResupply service;
|
|
- F(){worker.group=second.group=&cg;workerAgent.m_UtilityComponent=&workerUtility;secondAgent.m_UtilityComponent=&secondUtility;worker.agent=&workerAgent;second.agent=&secondAgent;sampler.group=&cg;sampler.members={&worker};crew.StatusSampler=&sampler;truck.pos={20};crew.Group=&cg;donor.Group=&dg;hq.crew=&crew;hq.donor=&donor;vehicle.Group=&dg;vehicle.Entity=&truck;service.truck=service.m_Truck=&vehicle;hq.services={&service};site.Definition=&def;site.Responders=&responders;responders.Group=&cg;site.Container=&cache;site.items={&item};cache.inventory=&inv;mode.sites.sites={&site};job.m_HQ=&hq;job.m_Mode=&mode;job.m_Worker=&worker;job.m_Original={&item};ERR_GroundDepotAccess::clear=true;}
|
|
+bool HQ::ReserveGroundDepot(ERR_HQUnit*c,ERR_HQUnit*d,ERR_InfantryResupply*s,int token,bool mineCargo){if(!(mineCargo?s->DepotMineNeeded():s->DepotNeeded())||!claim||c->LogisticsReservation||d->LogisticsReservation||s->truck->SupplyToken)return false;claims++;c->LogisticsReservation=d->LogisticsReservation=s->truck->SupplyToken=token;return true;}
|
|
+struct F{Mode mode;HQ hq;ERR_GroundDepotLoading job;Group cg,dg;ERR_HQUnit crew,donor;Definition def;ERR_SiteRuntime site,responders;Entity worker,second,cache,truck,item;Inventory inv;Slot itemSlot;InventoryItem carriedItem;Utility workerUtility,secondUtility;SCR_ChimeraAIAgent workerAgent,secondAgent;Sampler sampler;ERR_ConvoyVehicle vehicle;ERR_InfantryResupply service;
|
|
+ F(){itemSlot.attached=&item;carriedItem.slot=&itemSlot;item.carried=&carriedItem;item.parent=&truck;worker.group=second.group=&cg;workerAgent.m_UtilityComponent=&workerUtility;secondAgent.m_UtilityComponent=&secondUtility;worker.agent=&workerAgent;second.agent=&secondAgent;sampler.group=&cg;sampler.members={&worker};crew.StatusSampler=&sampler;truck.pos={20};crew.Group=&cg;donor.Group=&dg;hq.crew=&crew;hq.donor=&donor;vehicle.Group=&dg;vehicle.Entity=&truck;service.truck=service.m_Truck=&vehicle;hq.services={&service};site.Definition=&def;site.Responders=&responders;responders.Group=&cg;site.Container=&cache;site.items={&item};cache.inventory=&inv;mode.sites.sites={&site};job.m_HQ=&hq;job.m_Mode=&mode;job.m_Worker=&worker;job.m_Original={&item};ERR_GroundDepotAccess::clear=true;}
|
|
+ void Mine(){item.mine=true;service.mineNeeded=true;service.needed=false;worker.compatible=false;}
|
|
void Start(){job.Plan(0);assert(job.m_State==1&&hq.claims==1&&hq.orders==1);}
|
|
void Reach(){Start();job.Tick(0);assert(job.m_State==2&&vehicle.SupplyHeld&&vehicle.clears==1);job.Tick(1000);assert(donor.NextStatus==1000&&job.walks==0);job.Tick(2000);assert(job.m_State==2);hq.watch.report.ObservedAt=3000;job.Tick(3000);assert(job.m_State==3&&hq.orders==2&&job.walks==0);job.Tick(4000);assert(job.m_State==4&&job.walks==1);}
|
|
};
|
|
int main(){
|
|
+ // A mine-only deficit chooses the original porter without rifle compatibility,
|
|
+ // but still follows both delivered radio orders and exact two-leg proof.
|
|
+ {F f;f.Mine();f.Reach();assert(f.job.MineCargo()&&f.job.m_Worker==&f.worker);f.job.Tick(5000);auto*pick=f.job.m_Move;assert(pick->MineCargo());pick->unknown=false;pick->confirmed=true;pick->result=1;f.job.Tick(6000);f.job.Tick(7000);auto*drop=f.job.m_Move;assert(drop->MineCargo());drop->unknown=false;drop->confirmed=true;drop->result=1;assert(!f.service.AdmitDepot(&f.job,&f.item));f.job.Tick(8000);assert(f.job.m_State==6&&f.service.m_Stock.empty()&&f.service.m_MineStock.Count()==1&&f.service.m_DepotMineImports==1&&f.service.m_DepotImports==0);f.job.Tick(9000);assert(!f.job.Busy());}
|
|
+ // Armed or changed cargo aborts at each active stage; uncertain native moves
|
|
+ // retain claims until proof settles, and never import through a late callback.
|
|
+ for(int phase=1;phase<=5;phase++){F f;f.Mine();if(phase<=2){f.Start();if(phase==2)f.job.Tick(0);}else{f.Reach();if(phase==3)f.job.m_State=3;if(phase==5){f.job.Tick(5000);f.job.m_Move->unknown=false;f.job.m_Move->result=1;f.job.Tick(6000);}}
|
|
+ f.item.armed=true;f.job.Tick(10000);assert(!f.job.Busy()&&f.service.m_MineStock.empty()&&f.job.m_Delivered==0);}
|
|
+ {F f;f.Mine();f.Reach();f.job.Tick(5000);auto*leaf=f.job.m_Move;f.item.armed=true;f.job.Tick(6000);assert(f.job.m_State==7&&f.crew.LogisticsReservation>0);leaf->unknown=false;leaf->confirmed=true;leaf->result=1;f.job.Tick(7000);assert(!f.job.Busy()&&f.service.m_MineStock.empty()&&f.job.m_Delivered==0&&f.job.m_Spent.Count()==1);}
|
|
+ {F f;f.Mine();f.Reach();f.job.Tick(5000);f.job.m_Move->mineCargo=false;assert(!f.job.TransferValid(f.job.m_Move));}
|
|
+ {F f;f.Mine();f.Reach();f.job.Tick(5000);f.job.m_Move->unknown=false;f.job.m_Move->result=1;f.job.Tick(6000);f.job.Tick(7000);f.service.mineNeeded=false;assert(!f.job.ClaimDelivery(f.job.m_Move));f.job.m_Move->unknown=false;f.job.m_Move->confirmed=true;f.job.m_Move->result=1;f.job.Tick(8000);assert(f.service.m_MineStock.empty()&&f.job.m_Delivered==0);}
|
|
+ for(int kind=0;kind<5;kind++){F f;f.Mine();if(kind==0)f.item.armed=true;if(kind==1)f.item.defaultArmed=true;if(kind==2)f.item.alive=false;if(kind==3)f.item.collision=false;if(kind==4)f.service.mineNeeded=false;f.job.Plan(0);assert(!f.job.Busy()&&f.hq.orders==0&&f.hq.claims==0&&f.job.m_Attempts==0);}
|
|
+
|
|
// Actual Plan must enumerate worker/item pairs: the first fit original can
|
|
// carry the wrong weapon while a second original can use this exact magazine.
|
|
{F f;f.worker.well=2;f.second.well=1;f.sampler.members={&f.worker,&f.second};f.job.Plan(0);assert(f.job.m_State==1&&f.hq.claims==1&&f.hq.orders==1&&"compatible second original must permit planning despite incompatible first original");assert(f.job.m_Worker==&f.second&&f.job.m_WorkerSlot==1&&f.job.m_Item==&f.item&&f.job.m_Attempts==1);}
|
|
@@ -134,5 +151,5 @@
|
|
// A completed physical move cannot import wrong/previously-spent/foreign identity.
|
|
{F f;f.Reach();f.job.Tick(5000);auto*l=f.job.m_Move;l->unknown=false;l->result=1;f.job.Tick(6000);f.job.Tick(7000);f.job.m_Move->unknown=false;f.job.m_Move->confirmed=true;Entity foreign;assert(!f.service.AdmitDepot(&f.job,&foreign));f.service.m_Spent.Insert(&f.item);assert(!f.service.AdmitDepot(&f.job,&f.item));}
|
|
}
|
|
-'''.replace('METHODS',methods).replace('ADMISSION',admit.replace('bool AdmitDepot(', 'bool ERR_InfantryResupply::AdmitDepot(')))
|
|
+'''.replace('METHODS',methods).replace('ADMISSION',admit.replace('bool AdmitDepot(', 'bool ERR_InfantryResupply::AdmitDepot(').replace('bool AdmitDepotMine(', 'bool ERR_InfantryResupply::AdmitDepotMine(')))
|
|
print('PASS: actual depot mixed-roster worker/item selection, ownership/refusal and radio/planning/two-leg state transitions, fixed deadline, late proof quarantine, finite import and no adoption after Stop')
|
|
--- a/tests/test_ground_depot_transfer.py
|
|
+++ b/tests/test_ground_depot_transfer.py
|
|
@@ -9,7 +9,7 @@
|
|
for kind,field in {'InventoryStorageManagerComponent':'inventory','InventoryItemComponent':'item','BaseMagazineComponent':'magazine','DamageManagerComponent':'damage'}.items():
|
|
code=re.sub(kind+r'\.Cast\(([^;\n]*?)\.FindComponent\('+kind+r'\)\)',lambda m:m[1]+'.'+field,code)
|
|
code=code.replace('.FindComponent('+kind+')','.'+field)
|
|
-for name in ('EStoragePurpose','ERR_ForceRules','ERR_InfantryAmmoTransfer'):code=code.replace(name+'.',name+'::')
|
|
+for name in ('EStoragePurpose','ERR_ForceRules','ERR_InfantryAmmoTransfer','ERR_InfantryMineTransfer'):code=code.replace(name+'.',name+'::')
|
|
for name in ('m_Owner','m_From','m_To','m_Item','m_Source','m_Destination','m_FromSlot','m_FromStorage','m_ToStorage','m_Reservation','m_Callback','source','destination','item','inventoryItem','magazine','damage','inventory','storage','slot'):
|
|
code=re.sub(r'\b'+name+r'\.',name+'->',code)
|
|
code=code.replace(').',')->').replace('ERR_FiniteMagazineReservation ', 'ERR_FiniteMagazineReservation* ').replace('ERR_GroundDepotCallback ', 'ERR_GroundDepotCallback* ')
|
|
@@ -31,12 +31,15 @@
|
|
using IEntity=Entity*;using InventoryStorageManagerComponent=Inventory*;using InventoryStorageSlot=Slot*;using BaseInventoryStorageComponent=Storage*;using BaseMagazineComponent=Magazine*;using ERR_GroundDepotLoading=Owner*;
|
|
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};using InventoryItemComponent=Item*;
|
|
struct Damage{bool destroyed=false;bool IsDestroyed(){return destroyed;}};using DamageManagerComponent=Damage*;
|
|
-struct Entity{Entity*parent=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Damage*damage=nullptr;Inventory*inventory=nullptr;};
|
|
+struct Entity{Entity*parent=nullptr;Item*item=nullptr;Magazine*magazine=nullptr;Damage*damage=nullptr;Inventory*inventory=nullptr;bool mine=false,armed=false,defaultArmed=false,collision=true;};
|
|
struct Slot{Storage*storage=nullptr;IEntity attached=nullptr;Storage*GetStorage(){return storage;}IEntity GetAttachedEntity(){return attached;}};
|
|
struct Storage{IEntity owner=nullptr;IEntity GetOwner(){return owner;}};
|
|
struct Magazine{bool used=false,well=true;int rounds=30,capacity=30;bool IsUsed(){return used;}bool GetMagazineWell(){return well;}int GetAmmoCount(){return rounds;}int GetMaxAmmoCount(){return capacity;}};
|
|
struct ERR_ForceRules{static bool Finite(float x){return std::isfinite(x);}};
|
|
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity e,IEntity root){for(int i=0;e&&i<16;i++,e=e->parent)if(e==root)return true;return false;}};
|
|
+// ReadyMine is an explicit native-component boundary; its complete implementation
|
|
+// is covered by infantry mine transfer tests. This models exact inert PMN4 state.
|
|
+struct ERR_InfantryMineTransfer{static bool ReadyMine(IEntity e){return e&&e->mine&&e->damage&&!e->damage->destroyed&&!e->armed&&!e->defaultArmed&&e->collision;}};
|
|
struct EStoragePurpose{static const int PURPOSE_DEPOSIT=1;};
|
|
struct ScriptedInventoryOperationCallback{virtual void OnComplete(){}virtual void OnAllComplete(){}virtual void OnFailed(){}virtual void OnAllFailed(){}virtual ~ScriptedInventoryOperationCallback()=default;};
|
|
struct ERR_GroundDepotTransfer;
|
|
@@ -48,14 +51,35 @@
|
|
bool TryMoveItemToStorage(IEntity,Storage*,int,ScriptedInventoryOperationCallback*cb){calls++;last=cb;if(effect)effect();if(ack==1)cb->OnComplete();if(ack==-1)cb->OnFailed();return accept;}};
|
|
'''
|
|
main=r'''
|
|
-struct Fixture{Owner owner;Entity cache,porter,truck,mag,foreign;Inventory source,carry,cargo;Storage cs,ps,ts;Slot cslot,pslot,tslot;Item item;Magazine magazine;Damage damage;Array<IEntity>original,spent;ERR_FiniteMagazineReservation reservation;
|
|
+struct Fixture{bool mine=false;Owner owner;Entity cache,porter,truck,mag,foreign;Inventory source,carry,cargo;Storage cs,ps,ts;Slot cslot,pslot,tslot;Item item;Magazine magazine;Damage damage;Array<IEntity>original,spent;ERR_FiniteMagazineReservation reservation;
|
|
Fixture():reservation(&original,&spent,&mag){cache.inventory=&source;porter.inventory=&carry;truck.inventory=&cargo;source.owner=&cache;carry.owner=&porter;cargo.owner=&truck;cs.owner=&cache;ps.owner=&porter;ts.owner=&truck;source.storages={&cs};carry.storages={&ps};cargo.storages={&ts};carry.selected=&ps;cargo.selected=&ts;cslot.storage=&cs;pslot.storage=&ps;tslot.storage=&ts;cslot.attached=&mag;item.slot=&cslot;mag.item=&item;mag.magazine=&magazine;mag.damage=&damage;source.items={&mag};original={&mag};assert(reservation.Reserve());}
|
|
- ERR_GroundDepotTransfer Pickup(){return {&owner,&cache,&porter,&mag,&reservation,false};}
|
|
- ERR_GroundDepotTransfer Delivery(){return {&owner,&porter,&truck,&mag,&reservation,true};}
|
|
+ ERR_GroundDepotTransfer Pickup(){return {&owner,&cache,&porter,&mag ,&reservation,false,mine};}
|
|
+ ERR_GroundDepotTransfer Delivery(){return {&owner,&porter,&truck,&mag ,&reservation,true,mine};}
|
|
void Pick(){source.items.clear();carry.items={&mag};cslot.attached=nullptr;pslot.attached=&mag;item.slot=&pslot;}
|
|
void Deliver(){carry.items.clear();cargo.items={&mag};pslot.attached=nullptr;tslot.attached=&mag;item.slot=&tslot;}
|
|
};
|
|
int main(){
|
|
+ // Ready inert PMN4 uses the same exact native two-leg custody protocol,
|
|
+ // shared reservation and isolated callbacks, without a magazine component.
|
|
+ {Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto a=f.Pickup();assert(a.MineCargo()&&a.Tick(0)==0&&a.Unresolved());f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==1&&a.Confirmed());auto b=f.Delivery();assert(b.MineCargo()&&b.Tick(2)==0&&f.owner.claims==1);f.Deliver();f.carry.last->OnComplete();assert(b.Tick(3)==1&&b.Confirmed()&&!b.Unresolved()&&f.spent.Count()==1);}
|
|
+ // Every phase refuses changed/armed/default-armed/non-PMN4/destroyed identity.
|
|
+ for(int phase=0;phase<3;phase++)for(int kind=0;kind<5;kind++){Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto a=f.Pickup();
|
|
+ if(phase==1)assert(a.Tick(0)==0);
|
|
+ if(phase==2){assert(a.Tick(0)==0);f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==1);}
|
|
+ if(kind==0)f.mag.armed=true;
|
|
+ if(kind==1)f.mag.defaultArmed=true;
|
|
+ if(kind==2)f.mag.mine=false;
|
|
+ if(kind==3)f.damage.destroyed=true;
|
|
+ if(kind==4)f.mag.collision=false;
|
|
+ if(phase==2){auto b=f.Delivery();assert(b.Tick(2)==-1&&!b.Submitted()&&f.carry.calls==0&&!a.Confirmed());}
|
|
+ else{assert(a.Tick(1)==-1);assert(f.source.calls==(phase==1?1:0));if(phase==1)assert(a.Unresolved());}
|
|
+ }
|
|
+ // Inline native mutation after initial ready check cannot promote success.
|
|
+ {Fixture f;f.mine=f.mag.mine=true;auto a=f.Pickup();f.source.ack=1;f.source.effect=[&]{f.Pick();f.mag.armed=true;};assert(a.Tick(0)==-1&&!a.Confirmed()&&!a.Unresolved());}
|
|
+ {Fixture f;f.mine=f.mag.mine=true;auto a=f.Pickup();assert(a.Tick(0)==0);a.Stop();f.Pick();f.source.last->OnComplete();assert(a.Tick(1)==-1&&!a.Confirmed()&&!a.Unresolved()&&f.source.calls==1&&f.spent.Count()==1);}
|
|
+ {Fixture f;f.mine=f.mag.mine=true;f.source.accept=false;auto a=f.Pickup();assert(a.Tick(0)==-1&&a.Unresolved());f.damage.destroyed=true;assert(a.Unresolved());a.m_Item=nullptr;assert(!a.Unresolved()&&f.spent.Count()==1);}
|
|
+ {Fixture f;f.mine=f.mag.mine=true;f.mag.magazine=nullptr;auto wrong=ERR_GroundDepotTransfer(&f.owner,&f.cache,&f.porter,&f.mag,&f.reservation,false,false);assert(wrong.Tick(0)==-1&&!wrong.Submitted()&&f.source.calls==0);}
|
|
+
|
|
{Fixture f;auto a=f.Pickup();a.Acknowledge(true);assert(a.m_Ack==0);assert(a.Tick(0)==0&&a.Submitted()&&a.Unresolved());assert(f.reservation.CommittedFor(&f.mag));f.source.last->OnComplete();assert(a.Tick(1)==0);f.Pick();assert(a.Tick(2)==1&&a.Confirmed()&&!a.Unresolved());auto b=f.Delivery();assert(b.Tick(3)==0&&f.owner.claims==1);f.source.last->OnAllComplete();assert(b.Tick(4)==0);f.Deliver();assert(b.Tick(5)==0);f.carry.last->OnAllComplete();assert(b.Tick(6)==1&&b.Confirmed()&&!b.Unresolved());assert(f.source.calls==1&&f.carry.calls==1&&f.spent.Count()==1);}
|
|
{Fixture f;f.source.ack=1;f.source.accept=false;f.source.effect=[&]{assert(f.reservation.CommittedFor(&f.mag));assert(!f.reservation.ReleaseUnsubmitted());f.Pick();};auto a=f.Pickup();assert(a.Tick(0)==1&&!a.Unresolved());}
|
|
{Fixture f;f.source.accept=false;auto a=f.Pickup();assert(a.Tick(0)==-1&&a.Unresolved());a.Stop();f.Pick();f.source.last->OnComplete();assert(!a.Unresolved()&&!a.Confirmed()&&a.Tick(1)==-1);}
|
|
@@ -78,4 +102,4 @@
|
|
'''.replace('switch(bad){case0:break;}','')
|
|
main=main.replace(';if(bad', ';\n if(bad')
|
|
run(pre+translated_reservation()+code+'\n'.join(callbacks)+main)
|
|
-print('PASS: actual full depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified')
|
|
+print('PASS: actual full magazine/inert-PMN4 depot leaf, exact custody, isolated callbacks, finite reservation, false/unknown and Stop quarantine, once-only delivery and caps; native movement unqualified')
|