Files
EscapistsReintroduced/tests/test_infantry_mine_coordinator.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

83 lines
8.5 KiB
Python

"""Actual mine top-up selector plus the real shared finite reservation; native leaf is doubled."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
from test_finite_magazine_reservation import translated_reservation
p=Path(os.environ.get('ERR_INFANTRY_RESUPPLY_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_InfantryResupply.c'))
s=body(p,'protected void TransferMine(').replace('protected ','').replace('null','nullptr')
s=s.replace('ERR_InfantryMineTransfer.', 'ERR_InfantryMineTransfer::').replace('ERR_Actors.', 'ERR_Actors::').replace('vector.DistanceXZ','Vec::DistanceXZ')
s=s.replace('foreach (IEntity mine : m_MineStock)','for (IEntity mine : m_MineStock)')
s=s.replace('ERR_FiniteMagazineReservation reservation', 'ERR_FiniteMagazineReservation* reservation').replace('new ERR_FiniteMagazineReservation(m_MineStock, m_Spent, mine)', 'new ERR_FiniteMagazineReservation(&m_MineStock, &m_Spent, mine)')
for x in ['m_MineMove','m_Recipient','m_Truck','m_HQ','actor','m_Receiving','reservation']:s=s.replace(x+'.',x+'->')
for x in ['StatusSampler','Entity']:s=s.replace(x+'.',x+'->')
run(r'''
#include <cassert>
#include <string>
#include <vector>
#include <algorithm>
#include <cmath>
using string=std::string;
struct Vec{float x=0;static float DistanceXZ(Vec a,Vec b){return std::abs(a.x-b.x);}};
struct Group{};struct Mode{};struct Entity{bool ready=false,fight=true,owned=true,grant=true,designated=false;int mines=1;Vec pos;Vec GetOrigin(){return pos;}};using IEntity=Entity*;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T v){this->push_back(v);}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void RemoveItem(T v){this->erase(std::remove(this->begin(),this->end(),v),this->end());}};
'''+translated_reservation()+r'''
struct Sampler{Array<IEntity>original;int InitialStrength(){return original.Count();}IEntity InitialMember(int i){return original[i];}bool CanAttribute(int i,IEntity x,bool){return i>=0&&i<InitialStrength()&&original[i]==x&&x->owned;}};
struct Unit{struct Group*Group;Sampler*StatusSampler;};struct Truck{IEntity Entity;struct Group*Group;};
struct HQ{bool MineSupplyReady(Group*,IEntity x,bool replacement=false){return x&&x->grant&&(!replacement||x->designated);}};
struct ERR_Actors{static bool CanFight(IEntity x){return x&&x->fight;}};
struct ERR_InfantryMineTransfer{
static inline bool accept=true;static inline int made=0;IEntity item,actor;ERR_FiniteMagazineReservation*r;bool submitted=false;int result=0;
ERR_InfantryMineTransfer(Mode*,IEntity,Group*,IEntity a,Group*,IEntity i,ERR_FiniteMagazineReservation*q,Sampler*,int,bool empty=false):item(i),actor(a),r(q){assert(a->mines==(empty?0:1));made++;}
static bool ReadyMine(IEntity x){return x&&x->ready;}
static int CarriedCount(IEntity x,Group*,IEntity& found){found=x;return x?x->mines:-1;}
static IEntity Carrier(IEntity x,Group*){return x&&x->mines==1?x:nullptr;}
int Tick(float){if(accept&&!submitted){assert(r->Commit());submitted=true;}return result;}
bool Submitted(){return submitted;}
};
struct Service{
HQ*m_HQ;Mode*m_Mode;Unit*m_Recipient;Truck*m_Truck;IEntity m_Receiving=nullptr;bool m_EmptyMineReplacement=false;int m_MineRecipientSlot=-1,m_MinesDelivered=0,finishes=0;ERR_InfantryMineTransfer*m_MineMove=nullptr;
Array<IEntity>m_Members,m_MineStock,m_Spent,cargo;
bool Contains(IEntity,IEntity x){return cargo.Contains(x);}void Finish(string){finishes++;}
'''+s+r'''
};
struct Fixture{HQ h;Mode mode;Group g,d;Entity actor,other,truck,mine,second;Sampler sampler;Unit unit;Truck donor;Service s;
Fixture():sampler{{&actor,&other}},unit{&g,&sampler},donor{&truck,&d}{mine.ready=second.ready=true;s.m_HQ=&h;s.m_Mode=&mode;s.m_Recipient=&unit;s.m_Truck=&donor;s.m_Members={&actor,&other};s.m_MineStock={&mine,&second};s.cargo=s.m_MineStock;ERR_InfantryMineTransfer::accept=true;}
};
int main(){
{Fixture f;f.actor.mines=0;f.actor.designated=true;f.s.TransferMine(0);assert(f.s.m_MineMove&&f.s.m_MineMove->actor==&f.actor&&f.s.m_EmptyMineReplacement&&f.s.m_Spent.Count()==1);}
{Fixture f;f.actor.mines=-1;f.actor.designated=true;f.other.mines=0;f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.empty());}
{Fixture f;f.actor.mines=0;f.actor.designated=true;ERR_InfantryMineTransfer::accept=false;f.s.TransferMine(0);assert(f.s.m_Receiving==&f.actor&&f.s.m_EmptyMineReplacement);f.actor.mines=1;ERR_InfantryMineTransfer::accept=true;f.s.TransferMine(1);assert(!f.s.m_MineMove&&f.s.finishes==1&&f.s.m_Spent.empty());}
// Never grant mines to undesignated zero/two-mine members or to unsettled initial grants.
for(int refusal=0;refusal<6;refusal++){Fixture f;f.other.mines=0;
if(refusal==0)f.actor.mines=0;if(refusal==1)f.actor.mines=2;if(refusal==2)f.actor.grant=false;if(refusal==3)f.actor.owned=false;if(refusal==4)f.actor.pos.x=5;if(refusal==5)f.actor.fight=false;
f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.empty()&&!f.s.m_Receiving);}
// One exact finite original identity and the same shared spent list.
{Fixture f;f.s.TransferMine(0);auto*leaf=f.s.m_MineMove;assert(leaf&&leaf->actor==&f.actor&&leaf->item==&f.mine&&f.s.m_Spent.Count()==1&&f.s.m_Spent[0]==&f.mine);
f.s.TransferMine(1000);assert(f.s.m_MineMove==leaf&&f.s.m_Spent.Count()==1);leaf->result=1;f.s.TransferMine(2000);assert(!f.s.m_MineMove&&f.s.m_MinesDelivered==1&&f.s.finishes==1&&f.s.m_Spent.Count()==1);}
// Preflight refusal may release only before native submission, keeping actor pinned.
{Fixture f;ERR_InfantryMineTransfer::accept=false;f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.empty()&&f.s.m_Receiving==&f.actor);
f.actor.mines=0;ERR_InfantryMineTransfer::accept=true;f.s.TransferMine(1000);assert(!f.s.m_MineMove&&f.s.finishes==1&&f.s.m_Spent.empty());}
{Fixture f;ERR_InfantryMineTransfer::accept=false;f.s.TransferMine(0);f.actor.grant=false;ERR_InfantryMineTransfer::accept=true;f.s.TransferMine(1000);assert(!f.s.m_MineMove&&f.s.m_Spent.empty());}
// Captured/absent original or already-spent cargo is unavailable; no substitute.
{Fixture f;f.s.cargo.clear();f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.empty());}
{Fixture f;f.s.m_Spent=f.s.m_MineStock;f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.Count()==2);}
{Fixture f;for(int i=0;i<96;i++)f.s.m_Spent.Insert(&f.truck);f.s.TransferMine(0);assert(!f.s.m_MineMove&&f.s.m_Spent.Count()==96);}
// Failure delegates cleanup while retaining the submitted leaf and spent item.
{Fixture f;f.s.TransferMine(0);auto*leaf=f.s.m_MineMove;leaf->result=-1;f.s.TransferMine(1000);assert(f.s.m_MineMove==leaf&&f.s.finishes==1&&f.s.m_Spent.Count()==1);}
}
'''.replace('if(refusal==0)f.actor.mines=0;if(refusal==1)f.actor.mines=2;if(refusal==2)f.actor.grant=false;if(refusal==3)f.actor.owned=false;if(refusal==4)f.actor.pos.x=5;if(refusal==5)f.actor.fight=false;', 'if(refusal==0){f.actor.mines=0;}if(refusal==1){f.actor.mines=2;}if(refusal==2){f.actor.grant=false;}if(refusal==3){f.actor.owned=false;}if(refusal==4){f.actor.pos.x=5;}if(refusal==5){f.actor.fight=false;}'))
print('PASS: actual designated zero/one original carrier selection, settled startup, shared finite claims, no refund after submission')
h=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
w=body(h,'bool MineSupplyReady(').replace('SCR_AIGroup group','Group* group').replace('Replication.','Replication::').replace('m_Equipment.','m_Equipment->')
run(r'''
#include <cassert>
struct Group{};struct Item{};using IEntity=Item*;
struct Equipment{bool ready=false,designated=false;Group*group=nullptr;IEntity actor=nullptr;bool MineSupplyReady(Group*g,IEntity a,bool replacement=false){return ready&&g==group&&a==actor&&(!replacement||designated);}};
struct Replication{static inline bool server=true;static bool IsServer(){return server;}};
struct HQ{Equipment*m_Equipment=nullptr;bool active=true;bool MissionActive(){return active;}
'''+w+r'''};
int main(){Group group,other;Item actor;Equipment e;e.group=&group;e.actor=&actor;HQ h;assert(!h.MineSupplyReady(&group,&actor));h.m_Equipment=&e;assert(!h.MineSupplyReady(&group,&actor));e.ready=true;assert(h.MineSupplyReady(&group,&actor));assert(!h.MineSupplyReady(&group,&actor,true));e.designated=true;assert(h.MineSupplyReady(&group,&actor,true));assert(!h.MineSupplyReady(&other,&actor,true));assert(!h.MineSupplyReady(&other,&actor));h.active=false;assert(!h.MineSupplyReady(&group,&actor));h.active=true;Replication::server=false;assert(!h.MineSupplyReady(&group,&actor));}
''')
print('PASS: actual HQ startup grant readiness forwarding is authoritative and fail-closed')