Files
EscapistsReintroduced/tests/test_hq_multiconvoy_supply.py
T
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

73 lines
6.1 KiB
Python

"""Execute actual HQ multi-convoy planning with delivered-report boundaries doubled."""
import os, re, sys
from pathlib import Path
ROOT=Path(os.environ.get('ERR_REPO','/home/jakub/devel/escapistsrereintroduced'))
sys.path.insert(0,str(ROOT/'tests'))
from policy_harness import body,run
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_HQ.c'))
methods='\n'.join(body(p,s) for s in ('void PlanResupply(float now)','bool PlanResupplyTask(ERR_InfantryResupply supply, float now)'))
methods=methods.replace('null','nullptr').replace('foreach (','for (')
methods=methods.replace('ERR_HQUnit donor;','ERR_HQUnit donor=nullptr;')
for name in ('m_Resupply','supply','task','unit','donor','recipient','mounted','mineRecipient','launcherRecipient','m_Watch'):
methods=methods.replace(name+'.',name+'->')
methods=methods.replace('task->Vehicles[1].','task->Vehicles[1]->')
run(r'''
#include <cassert>
#include <vector>
#include <map>
#include <string>
using string=std::string;
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}};
struct Unit {int Group=0;string ForceSourceId;bool fighting=false;};using ERR_HQUnit=Unit*;
struct Entry {int value=0;};using ERR_HQWatchEntry=Entry*;
struct Watch {std::map<string,Entry*> delivered;Entry* Find(string id){return delivered.count(id)?delivered[id]:nullptr;}};
struct Vehicle{int Group=0;};
struct Task{bool Stopped=false,Ready=true;Array<Vehicle*> Vehicles;};using ERR_ConvoyTask=Task*;
struct Supply{
int state=1,calls=0,accepted=0,mountedCalls=0,mountedAccepted=0;int launcherCalls=0,launcherAccepted=0;bool launcher=false;bool infantry=true,mounted=false,mines=false;int mineCalls=0,mineAccepted=0;Task*task=nullptr;Unit* recipient=nullptr;Entry* demand=nullptr;Entry* donorReport=nullptr;
int State(){return state;}Task*Task(){return task;}
bool TryPlan(Unit*r,Unit*,Entry*d,Entry*s,float){calls++;if(!infantry||r!=recipient||!d||!s||d!=demand||s!=donorReport)return false;accepted++;state=2;return true;}
bool TryPlanMounted(Unit*r,Unit*,Entry*d,Entry*s,float){mountedCalls++;if(!mounted||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mountedAccepted++;state=6;return true;}
bool TryPlanLauncher(Unit*r,Unit*,Entry*d,Entry*s,float){launcherCalls++;if(!launcher||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;launcherAccepted++;state=2;return true;}
bool TryPlanMines(Unit*r,Unit*,Entry*d,Entry*s,float){mineCalls++;if(!mines||r!=recipient||d!=demand||s!=donorReport||!d||!s)return false;mineAccepted++;state=2;return true;}
};using ERR_InfantryResupply=Supply*;
struct Pool{Array<Supply*> services;int Count(){return services.Count();}Supply*At(int i){return services[i];}};
struct HQ{
Pool*m_Resupply=nullptr;int m_NextResupply=0;Array<Unit*>m_Units;Watch*m_Watch=nullptr;
bool ConvoyContact(Unit*u,float){return u->fighting;}
METHODS
};
struct Fixture{
Pool pool;Watch watch;HQ hq;Supply first,second;Task a,b;Vehicle va{1},vb{2};Unit da{1,"a"},db{2,"b"},recipient{3,"recipient"};Entry aStatus,bStatus,demand;
Fixture(){a.Vehicles={&va,&va,&va,&va,&va,&va};b.Vehicles={&vb,&vb,&vb,&vb,&vb,&vb};
first.task=&a;second.task=&b;first.recipient=second.recipient=&recipient;first.demand=second.demand=&demand;first.donorReport=&aStatus;second.donorReport=&bStatus;
watch.delivered={{"a",&aStatus},{"b",&bStatus},{"recipient",&demand}};pool.services={&first,&second};hq.m_Resupply=&pool;hq.m_Watch=&watch;hq.m_Units={&da,&db,&recipient};}
};
int main(){
{Fixture f;f.first.infantry=false;f.first.launcher=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.launcherAccepted==1&&f.first.mineCalls==0&&f.second.calls==0);}
{Fixture f;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.launcherCalls==0);}
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.launcher=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.launcherCalls==0);}
{Fixture f;f.first.infantry=false;f.first.mounted=false;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mineAccepted==1&&f.first.mountedCalls>0&&f.second.calls==0);}
{Fixture f;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.mineCalls==0);}
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.first.mines=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==1&&f.first.mineCalls==0);}
{Fixture f;f.first.mounted=true;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.first.mountedCalls==0);}
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.first.mountedAccepted==1&&f.second.calls==0);}
{Fixture f;f.first.infantry=false;f.first.mounted=true;f.recipient.fighting=true;f.hq.PlanResupply(100);assert(f.first.mountedAccepted==0);}
{Fixture f;f.first.state=0;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
{Fixture f;f.hq.PlanResupply(100);assert(f.first.accepted==1&&f.second.calls==0);f.first.state=1;
f.hq.PlanResupply(105);assert(f.second.accepted==1&&f.first.accepted==1);}
{Fixture f;f.a.Stopped=true;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
{Fixture f;f.da.fighting=true;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
{Fixture f;f.watch.delivered.erase("a");f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.second.accepted==1);}
{Fixture f;f.watch.delivered.erase("recipient");f.hq.PlanResupply(100);assert(f.first.accepted==0&&f.second.accepted==0);}
{Fixture f;f.pool.services={nullptr,&f.second};f.hq.m_NextResupply=17;f.hq.PlanResupply(100);assert(f.second.accepted==1);}
{Fixture f;f.a.Vehicles[1]=nullptr;f.hq.PlanResupply(100);assert(f.first.calls==0&&f.second.accepted==1);}
{Fixture f;f.pool.services.resize(21,&f.first);f.hq.PlanResupply(100);assert(f.first.calls==0);}
{Fixture f;f.pool.services.clear();f.hq.PlanResupply(100);f.hq.m_Resupply=nullptr;f.hq.PlanResupply(100);assert(f.first.calls==0);}
}
'''.replace('METHODS',methods).replace('Task*Task()','struct Task*Task()').replace('Task*task=','struct Task*task='))
print('PASS: actual HQ rotates finite donors, skips unavailable/contacted trucks, uses delivered reports and dispatches at most one trip per planning cadence')