Files
EscapistsReintroduced/tests/test_infantry_resupply_planning.py
jakub 6a667d8deb Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
2026-10-13 22:52:53 +02:00

305 lines
34 KiB
Python

"""Execute real finite-stock/planning/reservation bodies; native inventory/group APIs are doubles."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
P=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
A=Path(os.environ.get('ERR_INFANTRY_TRANSFER_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryAmmoTransfer.c'))
I=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_Inventory.c'
H=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
def cpp(s):
s=s.replace('protected ','').replace('notnull ','').replace('null','nullptr')
s=s.replace('map<string, int>', 'Map<int>*').replace('map<string, float>', 'Map<float>*')
s=s.replace('array<IEntity> items = {};','Array<IEntity> items;').replace('array<IEntity> raw = {};','Array<IEntity> raw;').replace('array<AIAgent> agents = {};','Array<AIAgent> agents;')
s=s.replace('out array<IEntity> items','Array<IEntity>& items').replace('array<IEntity> items','Array<IEntity>& items')
s=re.sub(r'foreach \((\w+) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
for typ in ('InventoryStorageManagerComponent','ERR_ForceRecord','ERR_SupplyConvoyTask'):
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
for name in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_InfantryMineTransfer','ERR_InfantryAmmoTransfer','ERR_ConvoyRules','ERR_ForceKind','ERR_ConvoyResources','ERR_Inventory','ERR_Actors','SCR_FactionManager','CompartmentAccessComponent','Replication','Math'):
s=s.replace(name+'.',name+'::')
s=s.replace('ERR_SupplyConvoyTask.Cast','ERR_SupplyConvoyTask::Cast').replace('FindComponent(BaseMagazineComponent)','HasMagazine()')
s=s.replace('vector.DistanceXZ','Vec::DistanceXZ').replace('InventoryStorageManagerComponent.Cast','InventoryStorageManagerComponent::Cast').replace('FindComponent(InventoryStorageManagerComponent)','FindComponent()')
for name in ('m_Mode','m_HQ','m_Convoy','m_Truck','m_Recipient','m_Donor','m_Move','m_MineMove','m_MineBatch','m_Mounted','recipient','donor','demand','donorStatus','entry','operation','inventory','item','owner','record','actor','agent','unit','m_MoveWaypoint','m_HoldWaypoint','magazine','supply','m_Consumed','m_NextAttempt'):
s=s.replace(name+'.',name+'->')
for name in ('Receipt','Entity','Group','Utility','StatusSampler','AmmoState','Combat','m_Perception'):
s=s.replace(name+'.',name+'->')
for name in ('Forces()','GetPrefabData()','Convoys()'):s=s.replace(name+'.',name+'->')
s=s.replace('ERR_SupplyConvoyTask* supply', 'Convoy* supply')
s=s.replace('bool designated;','bool designated=false;')
s=s.replace('int checks;', 'int checks=0;')
s=s.replace('bool available;','bool available=false;').replace('bool living;','bool living=false;')
return s
rules='\n'.join(cpp(body(P,x)) for x in ('static bool Fresh(', 'static bool Need(', 'static bool Pair(', 'static bool MinePair(', 'static bool LauncherNeed(', 'static bool LauncherPair('))
stock_method=('protected bool CompatibleStock(',) if 'protected bool CompatibleStock(' in P.read_text() else ()
methods='\n'.join(cpp(body(P,x)) for x in stock_method+('bool Bind(', 'ERR_ConvoyTask Task(', 'protected bool ConvoyCurrent(', 'protected bool Contains(', 'protected bool TruckValid(', 'protected bool Quiet(', 'protected bool Roster(', 'protected void Stock(', 'protected bool CaptureMines(', 'protected bool LauncherStock(', 'bool TryPlanLauncher(', 'bool TryPlanMines(', 'bool TryPlan(', 'protected void ClearWaypoints(', 'protected void Finish(', 'void Stop('))
reserve=cpp(body(H,'bool ReserveLogistics('))+cpp(body(H,'void ReleaseLogistics('))
unique=cpp(body(I,'static int AppendUnique('))+cpp(body(I,'static int Items('))
compatible=body(A,'static bool Compatible(')
for a,b in {
'BaseMagazineComponent.Cast(exactMagazine.FindComponent(BaseMagazineComponent))':'exactMagazine->ammoComp',
'BaseWeaponManagerComponent.Cast(actor.FindComponent(BaseWeaponManagerComponent))':'actor->weapons',
'BaseWeaponComponent.Cast(rifle.FindComponent(BaseWeaponComponent))':'rifle->weapon',
'InventoryItemComponent.Cast(loaded.GetOwner().FindComponent(InventoryItemComponent))':'loaded->GetOwner()->item',
'SCR_MuzzleInMagComponent.Cast(muzzle)':'(muzzle->inMag ? muzzle : nullptr)',
}.items():compatible=compatible.replace(a,b)
compatible=compatible.replace('array<','Array<').replace('foreach (','for (').replace('EWeaponType kind','int kind').replace('EMuzzleType muzzleKind','int muzzleKind')
for a in ['EWeaponType','EMuzzleType']:compatible=compatible.replace(a+'.',a+'::')
for a in ['magazine','manager','weapon','muzzle','reloadable','loaded','loadedItem','loadedSlot']:compatible=compatible.replace(a+'.',a+'->')
compatible=compatible.replace('GetMagazineWell().','GetMagazineWell()->')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <array>
#include <map>
#include <algorithm>
#include <cmath>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}bool IsEmpty(){return this->empty();}void Clear(){this->clear();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}};
template<class V>struct Map{std::map<string,V>data;V Get(string k){auto it=data.find(k);return it==data.end()?V{}:it->second;}void Set(string k,V v){data[k]=v;}};
struct Vec{float x=0,y=0,z=0;static float DistanceXZ(Vec a,Vec b){return std::hypot(a.x-b.x,a.z-b.z);}};
struct ERR_ForceRules{static bool Finite(float f){return std::isfinite(f);}static bool FiniteVector(Vec v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}};
struct Math{static float Min(float a,float b){return std::min(a,b);}};
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
struct Entity;using IEntity=Entity*;
struct Magazine;struct Manager;struct Weapon;struct Muzzle;struct Item;struct Slot;
using BaseMagazineComponent=Magazine*;using BaseWeaponManagerComponent=Manager*;using BaseWeaponComponent=Weapon*;using BaseMuzzleComponent=Muzzle*;using SCR_MuzzleInMagComponent=Muzzle*;using InventoryItemComponent=Item*;using InventoryStorageSlot=Slot*;
struct EWeaponType{static const int WT_RIFLE=1,WT_SNIPERRIFLE=2,WT_MACHINEGUN=3,WT_HANDGUN=4,WT_GRENADELAUNCHER=5,WT_ROCKETLAUNCHER=6;};
struct EMuzzleType{static const int MT_BaseMuzzle=0,MT_UGLMuzzle=1,MT_RPGMuzzle=2;};
struct Well{int type=1,parent=0;int Type(){return type;}bool IsInherited(int t){return type==t||parent==t;}};
struct Magazine{IEntity owner=nullptr;Well*well=nullptr;int rounds=30,capacity=30;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{bool inMag=true,canReload=true,disposable=false;Magazine*loaded=nullptr;Well*well=nullptr;int kind=0;bool CanBeReloaded(){return canReload;}bool IsDisposable(){return disposable;}Magazine*GetMagazine(){return loaded;}Well*GetMagazineWell(){return well;}int GetMuzzleType(){return kind;}};
struct Weapon{IEntity owner=nullptr;Array<Muzzle*>muzzles;int kind=1;IEntity GetOwner(){return owner;}int GetWeaponType(){return kind;}void GetMuzzlesList(Array<Muzzle*>&a){a=muzzles;}};
struct Manager{Array<IEntity>weapons;void GetWeaponsList(Array<IEntity>&a){a=weapons;}};
struct Slot{Muzzle*container=nullptr;IEntity attached=nullptr;Muzzle*GetParentContainer(){return container;}IEntity GetAttachedEntity(){return attached;}};
struct Item{Slot*slot=nullptr;Slot*GetParentSlot(){return slot;}};
struct InventoryStorageManagerComponent{Array<IEntity>items;void GetItems(Array<IEntity>& out){out=items;}static auto Cast(InventoryStorageManagerComponent* p){return p;}};
struct Prefab{string name="magazine";string GetPrefabName(){return name;}};
struct Group;using SCR_AIGroup=Group*;
struct Entity{Magazine*ammoComp=nullptr;Manager*weapons=nullptr;Weapon*weapon=nullptr;Item*item=nullptr;IEntity parent=nullptr;Prefab prefab;bool readyMine=false,magazine=true,hasPrefab=true,alive=true,player=false,fight=true,aboard=false,captured=false,released=false;int faction=1;Group* group=nullptr;InventoryStorageManagerComponent* inventory=nullptr;bool HasMagazine(){return magazine;}Prefab* GetPrefabData(){return hasPrefab?&prefab:nullptr;}InventoryStorageManagerComponent* FindComponent(){return inventory;}};
struct Agent{IEntity actor;IEntity GetControlledEntity(){return actor;}};using AIAgent=Agent*;
struct Waypoint{};
struct Group{Array<AIAgent>agents;bool slave=false;int players=0,faction=1;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}void GetAgents(Array<AIAgent>& out){out=agents;}void RemoveWaypoint(Waypoint*){}};
struct ERR_Actors{static bool Alive(IEntity a){return a&&a->alive;}static bool Player(IEntity a){return a&&a->player;}static SCR_AIGroup Group(IEntity a){return a->group;}static bool CanFight(IEntity a){return a&&a->alive&&a->fight;}static void DeleteSpawned(Waypoint*){}};
struct SCR_FactionManager{static int SGetFaction(IEntity a){return a->faction;}};
struct CompartmentAccessComponent{static bool GetVehicleIn(IEntity a){return a->aboard;}};
struct ERR_Inventory{
'''+unique+r'''
};
struct Ammo{bool LauncherComplete=true;int LauncherMuzzles=1,LauncherMagazines=1,LauncherRounds=0,LauncherChambers=0;bool MineComplete=true;int MineMembers=2,ReadyMines=1;bool Complete=true;int Members=2,SmallArmsMuzzles=2,SmallArmsRounds=60,SmallArmsChambers=0;bool Valid(int n){return Members==n;}};
struct Entry{bool Closed=false;int Count=2,Fit=2,MessageId=1;float ObservedAt=0;Vec Position;string SourceId;Ammo* AmmoState=nullptr;};using ERR_HQWatchEntry=Entry*;
struct ERR_InfantryResupplyRules{
'''+rules+r'''
};
namespace ERR_ForceKind{const int CONVOY=3;}
struct ERR_ForceRecord{bool Physical=true,Eliminated=false;int Kind=3,Id=2;};
struct Ledger{Group* owned=nullptr;ERR_ForceRecord record;ERR_ForceRecord* FindGroup(Group* g){return g==owned?&record:nullptr;}};
struct Mode{Ledger ledger;Ledger* Forces(){return &ledger;}int EnemyFaction(){return 1;}};
struct Perception{Array<int>m_aTargets;};
struct Utility{Perception* m_Perception=nullptr;};
struct Sampler{Group* group;Array<IEntity>original;int InitialStrength(){return original.Count();}IEntity InitialMember(int i){return original[i];}bool CanAttribute(int i,IEntity a){return original[i]==a&&!a->released;}bool OwnsRoster(Group* g){return g==group;}};
struct Unit{struct Group* Group=nullptr;struct Utility* Utility=nullptr;Sampler* StatusSampler=nullptr;string ForceSourceId;bool HomeGuard=false,eligible=true;int LogisticsReservation=0;void* Downlink=nullptr;void* PendingOrder=nullptr;void* PendingConvoyOrder=nullptr;void* Radio=this;void* Assignment=nullptr;void* Waypoint=nullptr;bool Eligible(Mode*){return eligible&&LogisticsReservation==0;}};using ERR_HQUnit=Unit*;
struct Combat{bool Held=false;};
struct Truck{int Index=1,SupplyToken=0;bool Registered=true,Released=false,Arrived=false,SupplyHeld=false,crew=true;struct Entity* Entity=nullptr;struct Group* Group=nullptr;struct Combat* Combat=nullptr;bool CrewValid(Mode*){return crew;}};
struct Receipt{bool Consumed=true;Array<int>Records={1,2,3,4,5,6};};
struct Cargo{bool Finished=true;};using ERR_ConvoyCargoCallback=Cargo*;
struct MineBatch{bool complete=true;Array<IEntity>items;bool Complete(){return complete;}int Count(){return items.Count();}IEntity At(int i){return items[i];}};
struct Convoy{MineBatch*MineStock=nullptr;bool supply=false,MagazinesIssued=true,CargoIssued=true,Stopped=false,Ready=true;struct Receipt* Receipt=nullptr;Array<Truck*>Vehicles;Array<struct Cargo*>Cargo;float Deadline=30000;};
using ERR_ConvoyTask=Convoy*;
struct ConvoyService{Array<Convoy*>tasks;int Count(){return tasks.Count();}Convoy* At(int index){return index>=0&&index<Count()?tasks[index]:nullptr;}};
struct ERR_SupplyConvoyTask:Convoy{static Convoy* Cast(Convoy* task){return task&&task->supply?task:nullptr;}};
struct ERR_ConvoyRules{static bool Captured(Entity* e,Group*){return e->captured;}};
struct ERR_ConvoyResources{static string Magazine(){return "magazine";}};
struct ERR_InfantryMineTransfer{static bool ReadyMine(IEntity x){return x&&x->readyMine;}};
struct Route{};struct Leaf{bool unresolved=false;void Stop(){}bool Unresolved(){return unresolved;}};struct Mounted{bool busy=false;bool Busy(){return busy;}void Stop(){}};
struct HQ{Mode* m_Mode;Array<ERR_HQUnit>m_Units;Convoy* convoy=nullptr;ConvoyService convoys;ConvoyService* Convoys(){return &convoys;}Route route;bool active=true,routeAvailable=true;int downlinks=0;bool MissionActive(){return active;}Convoy* ConvoyTask(){return convoy;}
bool designated=false;bool MineSupplyReady(Group*,IEntity,bool replacement=false){return !replacement||designated;}
Route* Downlink(Group*,string,string,int){downlinks++;return routeAvailable?&route:nullptr;}
'''+reserve+r'''
};
struct ERR_InfantryAmmoTransfer{static bool StoredUnder(IEntity item,IEntity actor){return item&&item->parent==actor;}
'''+compatible+r'''
};
struct Service{HQ* m_HQ;Mode* m_Mode;Convoy* m_Convoy=nullptr;Truck* m_Truck=nullptr;Unit* m_Recipient=nullptr;Unit* m_Donor=nullptr;Leaf* m_Move=nullptr;Leaf*m_MineMove=nullptr;MineBatch*m_MineBatch=nullptr;bool m_LauncherTrip=false,m_MineTrip=false,m_MineStockCaptured=false,m_EmptyMineReplacement=false;int m_MineRecipientSlot=-1;Mounted*m_Mounted=nullptr;Route* m_Order=nullptr;IEntity m_Receiving=nullptr;
Array<IEntity>m_Stock,m_MineStock,m_Spent,m_Members,m_Served;Map<int>*m_Consumed=new Map<int>();Map<float>*m_NextAttempt=new Map<float>();int m_ConvoySlot=-1;float m_NextTick=0;int m_State=0,m_Trip=0,m_Token=0,m_ReportId=0,m_TripDelivered=0;float m_Deadline=0,m_StockDeadline=-1;bool m_Stopped=false;
Waypoint* m_MoveWaypoint=nullptr;Waypoint* m_HoldWaypoint=nullptr;string reason;bool Current(){return !m_Stopped&&Replication::IsServer()&&m_HQ->active;}void Audit(string why){reason=why;}
'''+methods+r'''
};
struct Fixture{Mode mode;HQ hq;Service s;InventoryStorageManagerComponent inventory;Entity vehicle;std::array<Entity,140>items;std::array<Magazine,140>magazines;Entity rifle,launcher;Manager weapons;Weapon rifleWeapon,launcherWeapon;Muzzle rifleMuzzle,launcherMuzzle;Well well,wrongWell{2,0};Entity one,two;Agent a{&one},b{&two};Group recipients,donors;Utility recipientUtility,donorUtility;Sampler sampler;Unit recipient,donor;Combat combat;Truck truck;Receipt receipt;Cargo cargo;Convoy convoy;Ammo ammo;Entry demand,donorStatus;
Fixture():sampler{&recipients,{&one,&two}}{
hq.m_Mode=&mode;hq.convoy=&convoy;hq.convoys.tasks={&convoy};s.m_HQ=&hq;s.m_Mode=&mode;
one.weapons=&weapons;two.weapons=&weapons;weapons.weapons={&rifle,&launcher};rifle.weapon=&rifleWeapon;rifleWeapon.owner=&rifle;rifleWeapon.muzzles={&rifleMuzzle};rifleMuzzle.well=&well;
launcher.weapon=&launcherWeapon;launcherWeapon.owner=&launcher;launcherWeapon.kind=EWeaponType::WT_ROCKETLAUNCHER;launcherWeapon.muzzles={&launcherMuzzle};launcherMuzzle.well=&well;launcherMuzzle.kind=EMuzzleType::MT_RPGMuzzle;launcher.parent=&one;
for(int i=0;i<140;i++){items[i].ammoComp=&magazines[i];magazines[i].owner=&items[i];magazines[i].well=&well;}
vehicle.inventory=&inventory;vehicle.group=&donors;truck.Entity=&vehicle;truck.Group=&donors;truck.Combat=&combat;
mode.ledger.owned=&donors;convoy.Receipt=&receipt;convoy.Vehicles={&truck,&truck,&truck,&truck,&truck,&truck};convoy.Cargo={&cargo};
one.group=&recipients;two.group=&recipients;recipients.agents={&a,&b};recipient.Group=&recipients;recipient.Utility=&recipientUtility;recipient.StatusSampler=&sampler;recipient.ForceSourceId="recipient";
donor.Group=&donors;donor.Utility=&donorUtility;donor.ForceSourceId="donor";hq.m_Units={&recipient,&donor};
demand.SourceId="recipient";demand.AmmoState=&ammo;donorStatus.SourceId="donor";donorStatus.AmmoState=&ammo;
for(int i=0;i<48;i++)inventory.items.Insert(&items[i]);
}
bool Plan(float now=0){return s.TryPlan(&recipient,&donor,&demand,&donorStatus,now);}
void Refresh(float now,int id){demand.ObservedAt=now;donorStatus.ObservedAt=now;demand.MessageId=id;}
void Unchanged(){assert(s.m_Trip==0&&s.m_Consumed->Get("recipient")==0&&s.m_NextAttempt->Get("recipient")==0&&recipient.LogisticsReservation==0&&truck.SupplyToken==0&&hq.downlinks==0&&!truck.SupplyHeld&&!convoy.Stopped&&!s.m_Recipient&&!s.m_Donor);}
};
int main(){
// Actual Compatible must reject wrong-well, empty or malformed stock before
// any report/cooldown consumption, reservation, downlink or convoy hold.
for(int bad=0;bad<5;bad++){Fixture f;for(auto&mag:f.magazines){if(bad==0)mag.well=&f.wrongWell;if(bad==1)mag.rounds=0;if(bad==2)mag.capacity=0;if(bad==3)mag.rounds=31;if(bad==4)mag.well=nullptr;}f.s.Stock(0);assert(!f.Plan());f.Unchanged();}
{Fixture f;f.s.Stock(0);f.rifleMuzzle.well=&f.wrongWell;assert(!f.Plan());f.Unchanged();f.rifleMuzzle.well=&f.well;assert(f.Plan());}
// Same delivered recipient report remains available to a later compatible
// truck sharing the real per-source consumption/cooldown maps.
{Fixture first,second;Map<int> consumed;Map<float> cooldown;second.hq.convoys.tasks={&first.convoy,&second.convoy};second.hq.convoy=&first.convoy;second.hq.m_Units={&first.recipient,&second.donor};
assert(first.s.Bind(&first.convoy,0,&consumed,&cooldown));assert(second.s.Bind(&second.convoy,1,&consumed,&cooldown));
for(auto&mag:first.magazines){mag.well=&first.wrongWell;}first.s.Stock(0);second.s.Stock(0);assert(!first.Plan());first.Unchanged();
assert(second.s.TryPlan(&first.recipient,&second.donor,&first.demand,&second.donorStatus,0));assert(consumed.Get("recipient")==1&&cooldown.Get("recipient")==180000&&first.recipient.LogisticsReservation==100129&&second.hq.downlinks==1&&first.hq.downlinks==0);}
// A usable compatible item must still be a surviving unspent frozen identity.
{Fixture f;for(auto&mag:f.magazines)mag.rounds=0;f.magazines[48].rounds=30;f.s.Stock(0);f.inventory.items.Insert(&f.items[48]);assert(!f.Plan());f.Unchanged();f.magazines[0].rounds=30;f.s.m_Spent.Insert(&f.items[0]);assert(!f.Plan());f.Unchanged();f.s.m_Spent.Clear();assert(f.Plan());}
// Hard 32 original carriers and 96 captured magazines remain fail-closed.
{Fixture f;f.s.Stock(0);f.sampler.original.resize(33,&f.one);assert(!f.Plan());f.Unchanged();}
{Fixture f;f.s.Stock(0);for(int i=48;i<97;i++){f.s.m_Stock.Insert(&f.items[i]);f.inventory.items.Insert(&f.items[i]);}assert(!f.Plan());f.Unchanged();}
{Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);for(int i=0;i<95;i++)f.magazines[i].well=&f.wrongWell;f.s.Stock(0);assert(f.Plan());}
// Small-arms demand cannot borrow launcher compatibility, but the dedicated
// launcher path still supports its real empty reloadable muzzle and rejects disposables.
{Fixture f;f.weapons.weapons={&f.launcher};f.s.Stock(0);assert(!f.Plan());f.Unchanged();assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));}
{Fixture f;f.weapons.weapons={&f.launcher};f.launcherMuzzle.disposable=true;f.s.Stock(0);assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));f.Unchanged();}
{Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);f.s.Stock(0);
for(int i=0;i<95;i++)f.magazines[i].well=&f.wrongWell;
assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));
assert(f.s.m_LauncherTrip);}
{Fixture f;f.s.Stock(0);for(auto&mag:f.magazines)mag.well=&f.wrongWell;assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0)&&f.truck.SupplyToken==0);}
{Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;f.ammo.LauncherMagazines=0;assert(!f.Plan());assert(f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_LauncherTrip&&!f.s.m_MineTrip);}
for(int bad=0;bad<7;bad++){Fixture f;f.s.Stock(0);f.ammo.SmallArmsRounds=999;if(bad==0)f.ammo.LauncherComplete=false;if(bad==1)f.ammo.LauncherMuzzles=0;if(bad==2)f.ammo.LauncherRounds=1;if(bad==3)f.ammo.LauncherChambers=1;if(bad==4)f.ammo.Members=1;if(bad==5)f.demand.ObservedAt=-1;if(bad==6)f.demand.ObservedAt=1;
assert(!f.s.TryPlanLauncher(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_State==1&&f.truck.SupplyToken==0);}
// Additional mounted stock callbacks belong to the same initial freeze barrier.
{Fixture f;Cargo mounted;mounted.Finished=false;f.convoy.supply=true;f.convoy.Cargo.Insert(&mounted);
f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());
mounted.Finished=true;f.inventory.items.Insert(&f.items[48]);f.s.Stock(1000);
assert(f.s.m_State==1&&f.s.m_Stock.Count()==49&&f.s.m_Stock.Contains(&f.items[48]));}
// Bind accepts only an exact current slot and non-null shared demand histories.
{Fixture f;Map<int> consumed;Map<float> cooldown;Convoy other;
assert(!f.s.Bind(nullptr,0,&consumed,&cooldown));assert(!f.s.Bind(&f.convoy,-1,&consumed,&cooldown));
assert(!f.s.Bind(&f.convoy,20,&consumed,&cooldown));assert(!f.s.Bind(&f.convoy,1,&consumed,&cooldown));
assert(!f.s.Bind(&other,0,&consumed,&cooldown));assert(!f.s.Bind(&f.convoy,0,nullptr,&cooldown));assert(!f.s.Bind(&f.convoy,0,&consumed,nullptr));
assert(f.s.Bind(&f.convoy,0,&consumed,&cooldown));assert(f.s.Task()==&f.convoy);assert(!f.s.Bind(&f.convoy,0,&consumed,&cooldown));}
// Any prior startup/stock/trip/stop refuses late binding.
for(int began=0;began<6;began++){Fixture f;Map<int> consumed;Map<float> cooldown;
if(began==0){f.s.Stock(0);}if(began==1){f.s.m_NextTick=1000;}if(began==2){f.s.m_Trip=1;}
if(began==3){f.s.m_StockDeadline=90000;}if(began==4){f.s.m_Stopped=true;}if(began==5){f.s.m_State=1;}
assert(!f.s.Bind(&f.convoy,0,&consumed,&cooldown));}
// Second convoy remains valid independently of canonical first, but never follows replacement/reordering.
for(int mutation=0;mutation<3;mutation++){Fixture f;Convoy first,replacement;Map<int> consumed;Map<float> cooldown;
f.hq.convoy=&first;f.hq.convoys.tasks={&first,&f.convoy};assert(f.s.Bind(&f.convoy,1,&consumed,&cooldown));
f.s.Stock(0);assert(f.s.TruckValid()&&f.s.Task()==&f.convoy&&f.s.m_Stock.Count()==48);
if(mutation==0)f.hq.convoys.tasks[1]=&replacement;
if(mutation==1)std::swap(f.hq.convoys.tasks[0],f.hq.convoys.tasks[1]);
if(mutation==2)f.hq.convoys.tasks.resize(1);
assert(!f.s.TruckValid()&&!f.Plan()&&f.s.Task()==&f.convoy);}
// Canonical legacy startup also pins its first identity before readiness.
{Fixture f;Convoy replacement;f.convoy.Ready=false;f.s.Stock(0);assert(f.s.Task()==&f.convoy);
f.hq.convoy=&replacement;f.s.Stock(1000);assert(f.s.m_Stopped&&f.s.Task()==&f.convoy);}
// Every bound slot owns a disjoint 128-trip token range, including its last allowed trip.
for(int slot=0;slot<20;slot++){Fixture f;Map<int> consumed;Map<float> cooldown;
f.hq.convoys.tasks.resize(slot+1,nullptr);f.hq.convoys.tasks[slot]=&f.convoy;assert(f.s.Bind(&f.convoy,slot,&consumed,&cooldown));
f.s.Stock(0);assert(f.Plan());assert(f.s.m_Token==100000+slot*128+1);f.s.Finish("test released");
f.s.m_Trip=127;f.Refresh(180000,2);assert(f.Plan(180000));assert(f.s.m_Token==100000+(slot+1)*128);
f.s.Finish("test released");f.Refresh(360000,3);assert(!f.Plan(360000));}
// Separate convoys share report consumption and failed-attempt cooldowns for the same recipient source.
{Fixture first,second;Map<int> consumed;Map<float> cooldown;
second.hq.convoys.tasks={&first.convoy,&second.convoy};second.hq.convoy=&first.convoy;
assert(first.s.Bind(&first.convoy,0,&consumed,&cooldown));assert(second.s.Bind(&second.convoy,1,&consumed,&cooldown));
first.s.Stock(0);second.s.Stock(0);first.hq.routeAvailable=false;assert(first.Plan());
assert(consumed.Get("recipient")==1&&cooldown.Get("recipient")==180000);
second.Refresh(180000,1);assert(!second.Plan(180000));second.Refresh(179999,2);assert(!second.Plan(179999));
second.Refresh(180000,2);assert(second.Plan(180000));assert(first.s.m_Consumed->Get("recipient")==2&&first.s.m_NextAttempt->Get("recipient")==360000);}
// Actual inventory aggregation collapses repeated native storage references, not distinct identical-prefab magazines.
{Fixture f;f.inventory.items.Insert(&f.items[0]);f.inventory.items.Insert(nullptr);f.s.Stock(0);assert(f.s.m_State==1&&!f.s.m_Stopped&&f.s.m_Stock.Count()==48);assert(f.s.m_Stock[0]!=f.s.m_Stock[1]);}
// Malformed/non-magazine items never become spendable stock; native startup partial cargo stays partial.
{Fixture f;f.inventory.items={&f.items[0],&f.items[1],&f.items[2],nullptr};f.items[1].magazine=false;f.items[2].magazine=false;f.items[2].prefab.name="rifle";f.s.Stock(0);assert(f.s.m_Stock.Count()==1&&f.s.m_State==1);}
{Fixture f;f.cargo.Finished=false;f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());f.s.Stock(89999);assert(!f.s.m_Stopped&&f.s.m_Stock.IsEmpty());f.s.Stock(90000);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
{Fixture f;f.cargo.Finished=false;f.s.Stock(0);f.cargo.Finished=true;f.inventory.items={&f.items[0]};f.s.Stock(1000);assert(f.s.m_Stock.Count()==1&&!f.s.m_Stopped);}
{Fixture f;for(int i=48;i<97;i++)f.inventory.items.Insert(&f.items[i]);f.s.Stock(0);assert(f.s.m_Stopped&&!f.Plan());f.Unchanged();}
{Fixture f;for(auto& item:f.items)f.inventory.items.Insert(&item);f.s.Stock(0);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
{Fixture f;f.vehicle.inventory=nullptr;f.s.Stock(0);assert(f.s.m_Stopped&&!f.Plan());}
{Fixture f;f.convoy.Ready=false;f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());}
{Fixture f;f.receipt.Consumed=false;f.s.Stock(0);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
// Supply weapons settle after crew deadline; matching ammo must be issued and settled before snapshot.
{Fixture f;f.convoy.supply=true;f.convoy.MagazinesIssued=false;f.s.Stock(25000);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());
f.s.Stock(31000);assert(!f.s.m_Stopped&&f.s.m_State==0);f.convoy.MagazinesIssued=true;f.cargo.Finished=false;
f.s.Stock(32000);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());f.cargo.Finished=true;
f.items[0].prefab.name="NATO_556_STANAG";f.s.Stock(33000);assert(f.s.m_State==1&&!f.s.m_Stopped&&f.s.m_Stock.Contains(&f.items[0]));}
// Earliest admission: producer may finish weapons only at original Deadline+30000.
{Fixture f;f.convoy.supply=true;f.convoy.MagazinesIssued=false;f.cargo.Finished=false;f.s.Stock(0);
f.s.Stock(59999);assert(!f.s.m_Stopped&&f.s.m_Stock.IsEmpty());
f.convoy.MagazinesIssued=true;f.s.Stock(60000);assert(!f.s.m_Stopped&&f.s.m_State==0);
f.cargo.Finished=true;f.items[0].prefab.name="NATO_556_STANAG";f.s.Stock(89000);
assert(!f.s.m_Stopped&&f.s.m_State==1&&f.s.m_Stock.Contains(&f.items[0]));}
// Missing first-phase submission and missing second phase are bounded by one immutable deadline.
for(int phase=0;phase<2;phase++){Fixture f;f.convoy.supply=true;f.convoy.CargoIssued=phase!=0;f.convoy.MagazinesIssued=false;
f.s.Stock(25000);f.s.Stock(114999);assert(!f.s.m_Stopped&&f.s.m_StockDeadline==115000&&f.s.m_Stock.IsEmpty());
f.s.Stock(115000);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
// Faction catalogs may yield 96 distinct magazine identities; weapons are excluded.
{Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);f.items[0].prefab.name="NATO_556_STANAG";
f.items[96].magazine=false;f.inventory.items.Insert(&f.items[96]);f.s.Stock(0);
assert(!f.s.m_Stopped&&f.s.m_State==1&&f.s.m_Stock.Count()==96&&f.s.m_Stock.Contains(&f.items[0])&&!f.s.m_Stock.Contains(&f.items[96]));}
// Detached known-zero demand also requires settled original designation; unknown never authorizes.
{Fixture f;MineBatch batch;f.convoy.supply=true;f.convoy.MineStock=&batch;f.items[48].readyMine=true;batch.items={&f.items[48]};f.inventory.items.Insert(&f.items[48]);f.s.Stock(0);
for(int n: {0,2,3}){f.ammo.ReadyMines=n;assert(!f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));}
f.ammo.ReadyMines=0;f.hq.designated=true;f.one.released=f.two.released=true;assert(!f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));f.one.released=f.two.released=false;assert(f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));f.s.Finish("reset");f.s.m_Consumed->data.clear();f.s.m_NextAttempt->data.clear();f.hq.designated=false;
f.ammo.ReadyMines=1;f.ammo.MineComplete=false;assert(!f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));f.ammo.MineComplete=true;
batch.complete=false;assert(!f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));batch.complete=true;
assert(f.s.TryPlanMines(&f.recipient,&f.donor,&f.demand,&f.donorStatus,0));assert(f.s.m_MineTrip&&f.s.m_MineStock.Count()==1);}
// An accepted unresolved rifle move keeps both original claims across a new
// report after the shared180s cooldown. Distinct convoy slots own distinct tokens.
{Fixture first,second;Leaf leaf;leaf.unresolved=true;Map<int> consumed;Map<float> cooldown;
first.hq.convoys.tasks={&first.convoy,&second.convoy};first.hq.m_Units={&first.recipient,&first.donor,&second.donor};second.s.m_HQ=&first.hq;
assert(first.s.Bind(&first.convoy,0,&consumed,&cooldown));assert(second.s.Bind(&second.convoy,1,&consumed,&cooldown));
first.s.Stock(0);second.s.Stock(0);assert(first.Plan());first.s.m_Move=&leaf;first.s.Finish("accepted native rifle move remains unresolved");
assert(first.s.m_Move==&leaf&&leaf.Unresolved());
first.Refresh(180001,2);second.donorStatus.ObservedAt=180001;
assert(!second.s.TryPlan(&first.recipient,&second.donor,&first.demand,&second.donorStatus,180001));
assert(first.s.m_State==7&&first.recipient.LogisticsReservation==100001&&first.truck.SupplyToken==100001);
assert(consumed.Get("recipient")==1&&cooldown.Get("recipient")==180000&&second.s.m_Trip==0);
leaf.unresolved=false;first.s.Finish("settled");assert(first.recipient.LogisticsReservation==0);
assert(second.s.TryPlan(&first.recipient,&second.donor,&first.demand,&second.donorStatus,180001));
assert(first.recipient.LogisticsReservation==100129&&second.truck.SupplyToken==100129&&first.truck.SupplyToken==0);}
// A submitted unknown mine retains the real HQ claim across a newer delivered
// report and expired shared cooldown; a second truck cannot race the same actor.
{Fixture first,second;Leaf leaf;leaf.unresolved=true;first.s.Stock(0);assert(first.Plan());first.s.m_MineMove=&leaf;first.s.m_MineTrip=true;first.s.Finish("unknown native move");
assert(first.s.m_State==7&&first.recipient.LogisticsReservation==100001&&first.truck.SupplyToken==100001);
second.s.Stock(0);second.s.m_HQ=&first.hq;first.hq.convoy=&second.convoy;first.hq.convoys.tasks={&second.convoy};second.s.m_Consumed=first.s.m_Consumed;second.s.m_NextAttempt=first.s.m_NextAttempt;
first.Refresh(180001,2);second.donorStatus.ObservedAt=180001;
assert(!second.s.TryPlan(&first.recipient,&second.donor,&first.demand,&second.donorStatus,180001));assert(first.recipient.LogisticsReservation==100001);
leaf.unresolved=false;first.s.Finish("settled");assert(first.recipient.LogisticsReservation==0);
assert(second.s.TryPlan(&first.recipient,&second.donor,&first.demand,&second.donorStatus,180001));}
// Removed stock is unavailable; a different magazine added after capture does not replace its identity.
{Fixture f;f.s.Stock(0);f.inventory.items={&f.items[48]};assert(!f.Plan());f.Unchanged();f.inventory.items.Insert(&f.items[0]);assert(f.Plan());assert(f.s.m_Stock.Count()==48&&!f.s.m_Stock.Contains(&f.items[48]));}
{Fixture f;f.s.Stock(0);f.s.m_Spent=f.s.m_Stock;assert(!f.Plan());f.Unchanged();}
// A delivered report is tied to exact original source identities and original still-owned members.
{Fixture f;f.s.Stock(0);f.demand.SourceId="foreign";assert(!f.Plan());f.Unchanged();}
{Fixture f;f.s.Stock(0);f.donorStatus.SourceId="foreign";assert(!f.Plan());f.Unchanged();}
{Fixture f;f.s.Stock(0);f.one.released=true;assert(!f.Plan());f.Unchanged();assert(!f.s.m_Recipient&&!f.s.m_Donor);}
{Fixture f;f.s.Stock(0);Entity foreign;foreign.group=&f.recipients;Agent extra{&foreign};f.recipients.agents.Insert(&extra);assert(!f.Plan());f.Unchanged();}
// Real HQ force reservation refuses busy/support-ineligible or silent-radio recipients without consuming the demand.
for(int refusal=0;refusal<5;refusal++){Fixture f;f.s.Stock(0);void* owned=&f;
if(refusal==0){f.recipient.Assignment=owned;}if(refusal==1){f.recipient.Downlink=owned;}if(refusal==2){f.recipient.Waypoint=owned;}if(refusal==3){f.recipient.Radio=nullptr;}if(refusal==4){f.recipient.eligible=false;}
assert(!f.Plan());f.Unchanged();assert(!f.s.m_Recipient&&!f.s.m_Donor);
f.recipient.Assignment=nullptr;f.recipient.Downlink=nullptr;f.recipient.Waypoint=nullptr;f.recipient.Radio=&f;f.recipient.eligible=true;
assert(f.Plan());assert(f.s.m_Trip==1&&f.s.m_Consumed->Get("recipient")==1&&f.recipient.LogisticsReservation==100001);}
{Fixture f;f.s.Stock(0);f.recipient.LogisticsReservation=7;assert(!f.Plan());assert(f.recipient.LogisticsReservation==7&&f.s.m_Trip==0&&f.hq.downlinks==0);}
// Failed truck downlink is still a consumed planning attempt; cleanup preserves all cargo and other ownership.
{Fixture f;f.s.Stock(0);f.hq.routeAvailable=false;assert(f.Plan());assert(f.s.m_State==1&&f.s.m_Trip==1&&f.s.m_Consumed->Get("recipient")==1&&f.recipient.LogisticsReservation==0&&f.truck.SupplyToken==0&&f.inventory.items.Count()==48);
f.hq.routeAvailable=true;f.Refresh(180000,1);assert(!f.Plan(180000)); // same consumed message cannot replay after cooldown
f.Refresh(179999,2);assert(!f.Plan(179999));f.Refresh(180000,2);assert(f.Plan(180000));assert(f.s.m_Trip==2&&f.s.m_Deadline==300000);}
// A stale-but-still-admissible demand gets only the remaining evidence horizon, never a new full lifetime.
{Fixture f;f.s.Stock(0);assert(f.Plan(120000));assert(f.s.m_Deadline==180000);assert(!f.Plan(120000)&&f.hq.downlinks==1);}
// Independent guarded failures do not leak reservations or acquire replacement force identities.
for(int refusal=0;refusal<7;refusal++){Fixture f;f.s.Stock(0);
if(refusal==0){f.recipient.HomeGuard=true;}if(refusal==1){f.truck.Combat->Held=true;}if(refusal==2){f.vehicle.captured=true;}if(refusal==3){f.mode.ledger.record.Eliminated=true;}if(refusal==4){f.donor.PendingConvoyOrder=&f;}if(refusal==5){f.ammo.Complete=false;}if(refusal==6){f.demand.Position.x=201;}
assert(!f.Plan());f.Unchanged();}
}
''')
print('PASS: actual Compatible and bounded stock preflight before report/claim consumption, later compatible truck, launcher controls, convoy binding, finite identities, disjoint tokens, shared report replay/cooldown, original recipients and HQ cleanup')