Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
94 lines
6.6 KiB
Python
94 lines
6.6 KiB
Python
"""Execute HQ hunter reservations: one existing owner, delivered status, finite caps."""
|
|
import re
|
|
from policy_harness import ROOT, body, run
|
|
|
|
p = ROOT / 'addons/core/Scripts/Game/ERR/10_AI/ERR_HQHunterOperations.c'
|
|
source = p.read_text()
|
|
record = body(p, 'class ERR_HQHunterCommitment').replace('class ', 'struct ') + ';'
|
|
methods = '\n'.join(body(p, signature) for signature in (
|
|
'protected ERR_HQHunterCommitment HunterCommitment(',
|
|
'protected void PruneHunterOperations(',
|
|
'override int CoordinatedHunterCount(',
|
|
'override bool CoordinatedHunterCandidate(',
|
|
'override bool CoordinatedHunterQueued(',
|
|
'override void CoordinatedHunterDelivery(',
|
|
'override void ClearCoordinatedHunters(',
|
|
))
|
|
|
|
def cpp(text):
|
|
text = text.replace('protected ', '').replace('override ', '').replace('out vector ', 'vector& ')
|
|
for cls in ('ERR_HQHunterCommitment', 'ERR_HQWatchEntry', 'ERR_HQBelief'):
|
|
text = re.sub(r'\b' + cls + r' (\w+)', cls + r'* \1', text)
|
|
text = text.replace('struct ERR_HQHunterCommitment*', 'struct ERR_HQHunterCommitment')
|
|
text = text.replace('foreach (ERR_HQHunterCommitment* slot : m_HunterOperations)', 'for (ERR_HQHunterCommitment* slot : m_HunterOperations)')
|
|
text = text.replace('m_HunterOperations[i].', 'm_HunterOperations[i]->')
|
|
for name in ('slot', 'entry', 'belief', 'm_Mode'):
|
|
text = text.replace(name + '.', name + '->')
|
|
for cls in ('ERR_HQMemoryRules', 'ERR_HQOperationRules', 'Math', 'vector', 'string'):
|
|
text = text.replace(cls + '.', cls + '::')
|
|
text = text.replace('null', 'nullptr')
|
|
text = re.sub(r'\b(int|float|bool) (\w+);', r'\1 \2{};', text)
|
|
return text
|
|
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <map>
|
|
struct string:std::string {using std::string::string; template<class...T>static string Format(string,T...){return "event";}};
|
|
struct vector {float x=0,y=0,z=0;static float DistanceXZ(vector a,vector b){return std::hypot(a.x-b.x,a.z-b.z);}};
|
|
struct Math {static float Min(float a,float b){return std::min(a,b);}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;int Count(){return this->size();}void Insert(T a){this->push_back(a);}void Remove(int i){this->erase(this->begin()+i);}void Clear(){this->clear();}};
|
|
struct ERR_HQMemoryRules {static bool DeliveryFresh(float at,float now){return at>=0&&now>=at&&now-at<=120000;}};
|
|
struct ERR_HQOperationRules {static int GlobalLimit(int n){return std::clamp(n,0,8);}};
|
|
struct ERR_HQBelief {int BeliefId=1;float ObservedAt=1000;vector Position{100,0,100};bool valid=true;bool Actionable(){return valid;}};
|
|
struct ERR_HQWatchEntry{bool Closed=false;int Fit=4;float ObservedAt=1000;vector Position{200,0,100};};
|
|
struct Watch{std::map<string,ERR_HQWatchEntry> entries;bool empty=false;ERR_HQWatchEntry*Find(string id){auto i=entries.find(id);return i==entries.end()?nullptr:&i->second;}bool ArmedSearchBlocked(string,float){return empty;}int SquadLimit(int n){return n;}};
|
|
struct Settings{int limit=8;int Runtime(string key){return key=="supportSquadLimit"?limit:2500;}};
|
|
struct Mode{struct Settings settings;struct Settings&Settings(){return settings;}};
|
|
struct Operations{int n=0;int Count(int,float){return n;}};
|
|
struct Journal{template<class...T>void Write(T...){}};
|
|
''' + cpp(record) + r'''
|
|
struct HQ{
|
|
bool m_Coordinated=true;Array<ERR_HQHunterCommitment*>m_HunterOperations;Watch m_Watch;Mode mode;Mode*m_Mode=&mode;Operations m_Operations;Journal m_Journal;int infantry=0,mounted=0,retainedCommitments=0;
|
|
int CoordinatedResponseAssigned(float){return infantry+retainedCommitments;}int ResponseAssigned(){return infantry;}int CoordinatedMountedCount(int){return mounted;}
|
|
''' + cpp(methods) + r'''
|
|
};
|
|
int main(){
|
|
HQ h;ERR_HQBelief b;vector pos;
|
|
assert(!h.CoordinatedHunterCandidate("crew",&b,2000,pos)); // no own check-in, no oracle
|
|
h.m_Watch.entries["crew"]={};assert(h.CoordinatedHunterCandidate("crew",&b,2000,pos)&&pos.x==200);
|
|
h.infantry=8;assert(!h.CoordinatedHunterCandidate("crew",&b,2000,pos));h.infantry=0;
|
|
h.retainedCommitments=8;assert(!h.CoordinatedHunterCandidate("crew",&b,2000,pos));h.retainedCommitments=0;
|
|
h.m_Operations.n=5;h.mounted=1;assert(!h.CoordinatedHunterCandidate("crew",&b,2000,pos));h.mounted=0;
|
|
assert(h.CoordinatedHunterQueued("crew",&b,{500,0,100},10,2000));
|
|
assert(h.CoordinatedHunterCount(2000)==1&&h.CoordinatedHunterCount(2000,1)==1);
|
|
assert(!h.CoordinatedHunterCandidate("new",&b,2000,pos));
|
|
h.m_Watch.entries["new"]={};assert(!h.CoordinatedHunterCandidate("new",&b,2000,pos)); // sixth slot already pending
|
|
h.CoordinatedHunterDelivery("crew",999,true,3000);assert(h.HunterCommitment("crew")->OperationId==0);
|
|
h.CoordinatedHunterDelivery("crew",10,true,3000);assert(h.HunterCommitment("crew")->OperationId==1);
|
|
// A pending replacement reserves both operations, but still one physical crew.
|
|
h.m_Operations.n=0;ERR_HQBelief other;other.BeliefId=2;other.ObservedAt=4000;
|
|
assert(h.CoordinatedHunterQueued("crew",&other,{600,0,100},11,5000));
|
|
assert(h.CoordinatedHunterCount(5000)==1&&h.CoordinatedHunterCount(5000,1)==1&&h.CoordinatedHunterCount(5000,2)==1);
|
|
h.CoordinatedHunterDelivery("crew",11,false,6000);assert(h.CoordinatedHunterCount(6000,1)==1&&h.CoordinatedHunterCount(6000,2)==0);
|
|
assert(h.CoordinatedHunterQueued("crew",&other,{600,0,100},12,7000));h.CoordinatedHunterDelivery("crew",12,true,8000);
|
|
assert(h.CoordinatedHunterCount(8000,1)==0&&h.CoordinatedHunterCount(8000,2)==1);
|
|
// Replaying the same observation never renews the ten-minute deadline.
|
|
h.m_Watch.entries["crew"].ObservedAt=500000;
|
|
assert(h.CoordinatedHunterQueued("crew",&other,{600,0,100},13,500000));h.CoordinatedHunterDelivery("crew",13,true,500001);
|
|
assert(h.HunterCommitment("crew")->ExpiresAt==604000&&h.CoordinatedHunterCount(604000)==0);
|
|
h.m_Watch.entries["crew"].ObservedAt=605000;other.ObservedAt=605000;
|
|
assert(h.CoordinatedHunterQueued("crew",&other,{600,0,100},14,606000));
|
|
h.m_Watch.entries["crew"].Closed=true;assert(h.CoordinatedHunterCount(607000)==0);
|
|
h.m_Watch.entries["crew"].Closed=false;h.m_Watch.empty=true;assert(!h.CoordinatedHunterCandidate("crew",&other,607000,pos));
|
|
h.m_Watch.empty=false;assert(!h.CoordinatedHunterCandidate("crew",&other,800000,pos)); // stale status
|
|
h.ClearCoordinatedHunters();assert(h.m_HunterOperations.Count()==0);
|
|
}
|
|
''')
|
|
candidate = body(p, 'override bool CoordinatedHunterCandidate(')
|
|
assert 'GetOrigin' not in candidate and 'm_Watch.Find(source)' in candidate
|
|
print('PASS: actual hunter reservation ledger, pending/delivered replacement, independent six/eight caps, stale/empty status, finite observation deadline, no hidden force-position lookup')
|