Files
EscapistsReintroduced/tests/test_hq_local_handoff_intake.py
T

70 lines
7.0 KiB
Python

"""Execute actual HQ route choice/visual queue/caller exclusion with radio boundary doubles."""
import os,re
from pathlib import Path
from policy_harness import ROOT,body,run
p=Path(os.environ.get('ERR_HQ_SOURCE',ROOT/'addons/ai/Scripts/Game/ERR/ERR_HQ.c'))
s='\n'.join(body(p,x) for x in ('protected bool OriginOccupied(', 'protected ERR_HQRoute ObservationRoute(', 'protected void Observe(', 'protected void QueueReports('))
s=s.replace('protected ','').replace('array<IEntity>','Arr<IEntity>').replace('array<ref ERR_HQVisualContact>','Arr<ERR_HQVisualContact*>').replace('foreach (','for (')
for name in ('ERR_HQUnit','ERR_HQRoute','ERR_HQReport','ERR_HQVisualContact'):
s=re.sub(r'\b'+name+r' (\w+)',name+r'* \1',s)
for name in ('unit','other','route','own','seen','report'):
s=s.replace(name+'.',name+'->')
for name in ('Uplink','Task'):
s=s.replace(name+'.',name+'->')
s=s.replace('Nodes[0].','Nodes[0]->').replace('m_Mode.Settings().Get','m_Mode->Settings()->Get').replace('ERR_HQObservation.','ERR_HQObservation::').replace('null','nullptr')
s=s.replace('ERR_HQRoute* own;','ERR_HQRoute* own=nullptr;').replace('ERR_HQRoute* route;','ERR_HQRoute* route=nullptr;')
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
#include <string>
using IEntity=void*;
using string=std::string;
template<class T>struct Arr:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;bool IsEmpty(){return this->empty();}int Count(){return this->size();}bool Contains(T v){return std::find(this->begin(),this->end(),v)!=this->end();}void Insert(T v){this->push_back(v);}};
struct Group{};
struct Node{IEntity Carrier=nullptr;bool valid=true;bool Valid(bool){return valid;}};
struct ERR_HQRoute{bool Originated=false,Failed=false;Group*LocalSource=nullptr;Arr<Node*> Nodes;int MessageId=0;string Payload;};
struct ERR_HQVisualContact{IEntity Entity=nullptr;int Position=91;float ObservedAt=900;};
struct Utility{bool visible=true;ERR_HQVisualContact sample;};
struct ERR_HQObservation{static void Gather(Utility*u,float,Arr<ERR_HQVisualContact*>&out){if(u->visible)out.Insert(&u->sample);}template<class T,class A>static void Describe(T*,A&,float){} };
struct Selector{int queues=0;ERR_HQVisualContact*Select(Arr<ERR_HQVisualContact*>&s){return s[0];}void Queued(IEntity){queues++;}};
struct History{IEntity target=nullptr;float time=-1;bool CanQueue(IEntity t,float at,float){return t&&!(t==target&&at<=time);}void Queued(IEntity t,float at,float){target=t;time=at;}};
struct ERR_HQReport{int MessageId=0,Position=0;IEntity Target=nullptr;Group*Source=nullptr;float ObservedAt=0;string Description(float){return "unaltered source sighting";}};
struct Settings{int Get(string){return 5;}};
struct Mode{struct Settings values;struct Settings*Settings(){return &values;}};
struct Task{float ArrivedAt=0;};
struct ERR_HQUnit{struct Group*Group=nullptr;struct Utility*Utility=nullptr;IEntity Radio=nullptr;float NextReport=0;ERR_HQRoute*Uplink=nullptr;ERR_HQReport*PendingReport=nullptr;Selector ReportSelection;History SentObservations;int StatusSampler=1;struct Task*Task=nullptr;};
struct Network{ERR_HQRoute*own=nullptr,*local=nullptr;IEntity ownCarrier=nullptr;int attempts=0;Arr<IEntity>lastBusy;
ERR_HQRoute*Route(IEntity caller){return caller==ownCarrier?own:nullptr;}
ERR_HQRoute*LocalRoute(Group*source,int,Arr<IEntity>&busy){attempts++;lastBusy=busy;if(!local||busy.Contains(local->Nodes[0]->Carrier))return nullptr;local->LocalSource=source;return local;}};
struct Journal{int next=0,reports=0;int Message(){return ++next;}void Write(string kind,string,int){if(kind=="REPORT")reports++;}};
struct HQ{Mode mode;Mode*m_Mode=&mode;Network m_Network;Journal m_Journal;Arr<ERR_HQUnit*>m_Units;int statuses=0,bodies=0,sweeps=0;
void StatusReport(ERR_HQUnit*,float){statuses++;}void ObserveBody(ERR_HQUnit*,float){bodies++;}void Sweep(ERR_HQUnit*,float){sweeps++;}
''' + s.replace('static void Describe(auto*,auto&,float)','template<class T,class A>static void Describe(T*,A&,float)') + r'''
};
int main(){
HQ h;Group sourceGroup,receiverGroup;int witness,carrier,target;Utility utility;utility.sample.Entity=&target;ERR_HQUnit source,receiver;source.Group=&sourceGroup;source.Utility=&utility;receiver.Group=&receiverGroup;receiver.Radio=&carrier;Node node;node.Carrier=&carrier;ERR_HQRoute local;local.Nodes={&node};h.m_Network.local=&local;h.m_Units={&source,&receiver};
Utility quietUtility;quietUtility.visible=false;ERR_HQUnit quiet;quiet.Group=&sourceGroup;quiet.Utility=&quietUtility;
h.Observe(&quiet,0);assert(h.m_Network.attempts==0&&quiet.NextReport==5000);
h.Observe(&source,1000);assert(source.Uplink==&local&&source.PendingReport&&h.m_Journal.reports==1&&source.ReportSelection.queues==1);
auto report=source.PendingReport;assert(report->Source==&sourceGroup&&report->Target==&target&&report->ObservedAt==900&&report->Position==91&&report->MessageId==local.MessageId);
assert(receiver.PendingReport==nullptr&&receiver.Uplink==nullptr); // No recipient-owned rewrite or direct HQ intake.
h.Observe(&source,1001);assert(source.PendingReport==report&&h.m_Journal.reports==1);
// Receiver cannot originate its own reports during the source's physical origin hold.
Task task;task.ArrivedAt=500;receiver.Task=&task;assert(h.OriginOccupied(&carrier,&receiver));h.QueueReports(&receiver,1001);assert(task.ArrivedAt==-1&&h.statuses==0&&h.sweeps==0);
ERR_HQUnit contender;contender.Group=&sourceGroup;contender.Utility=&utility;assert(!h.ObservationRoute(&contender));
local.Originated=true;assert(!h.OriginOccupied(&carrier,&receiver));
// Explicit own pending uplink also excludes a candidate even after its origin phase.
source.Uplink=nullptr;receiver.Uplink=&local;assert(!h.ObservationRoute(&contender));receiver.Uplink=nullptr;
// Identical observed lineage never receives a second message id even after failure/slot release.
source.NextReport=0;h.Observe(&source,1100);assert(!source.Uplink&&h.m_Journal.reports==1&&source.ReportSelection.queues==1);
utility.sample.ObservedAt=1101;source.NextReport=0;h.Observe(&source,1101);assert(source.Uplink==&local&&h.m_Journal.reports==2&&source.PendingReport->ObservedAt==1101);
// Prefer an actual own route; a currently invalid own caller falls back to the nearby usable route.
source.Uplink=nullptr;Node ownNode;ownNode.Carrier=&witness;ERR_HQRoute own;own.Nodes={&ownNode};source.Radio=&witness;h.m_Network.ownCarrier=&witness;h.m_Network.own=&own;
assert(h.ObservationRoute(&source)==&own);ownNode.valid=false;assert(h.ObservationRoute(&source)==&local);
// A blocked fallback throttles the next no-radio attempt and creates no report.
source.Radio=nullptr;h.m_Network.local=nullptr;source.NextReport=0;utility.sample.ObservedAt=2000;int messages=h.m_Journal.next;h.Observe(&source,2000);assert(source.NextReport==7000&&!source.Uplink&&h.m_Journal.next==messages);
}
'''.replace('static void Describe(auto*,auto&,float)','template<class T,class A>static void Describe(T*,A&,float)'))
print('PASS: actual visual intake retains source/time/position/id, rejects replay, prefers usable own route, excludes simultaneous callers and conservatively resets negative-sweep dwell')